RValue references, pointers, and copy constructors
Consider the following piece of code:
int three() {
return 3;
}
template <typename T>
class Foo {
private:
T* ptr;
public:
void bar(T& t) { ptr = new T(t); }
void bar(const T& t) { ptr = new T(t); }
void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
};
int main() {
Foo<int> foo;
int a = 3;
const int b = 3;
foo.bar(a); // <--- Calls Foo::bar(T& t)
foo.bar(b); // <--- Calls Foo::bar(const T& t)
foo.bar(three()); // <--- Calls Foo::bar(T&& t); Runs fine, but only if either of the other two are called first!
return 0;开发者_高级运维
}
My question is, why does the third overload Foo::bar(T&& t)
crash the program? What exactly is happening here? Does the parameter t
get destroyed after the function returns?
Furthermore, let's assume that the template parameter T
was a very large object with a very costly copy constructor. Is there any way to use RValue References to assign it to Foo::ptr
without directly accessing this pointer and making a copy?
In this line
void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
you can dereference an uninitialized pointer. This is undefined behavior.
You must call one of the two other version of bar first because you need to create the memory for your object.
So I would do ptr = new T(std::move(t));
.
If your type T supports moving the move constructor will get called.
Update
I would suggest something like that. Not sure if you need the pointer type within foo:
template <typename T>
class Foo {
private:
T obj;
public:
void bar(T& t) { obj = t; } // assignment
void bar(const T& t) { obj = t; } // assignment
void bar(T&& t) { obj = std::move(t); } // move assign
};
This would avoid memory leaks which are also quite easy with your approach.
If you really need the pointer in your class foo how about that:
template <typename T>
class Foo {
private:
T* ptr;
public:
Foo():ptr(nullptr){}
~Foo(){delete ptr;}
void bar(T& t) {
if(ptr)
(*ptr) = t;
else
ptr = new T(t);
}
void bar(const T& t) {
if(ptr)
(*ptr) = t;
else
ptr = new T(t);
}
void bar(T&& t) {
if(ptr)
(*ptr) = std::move(t);
else
ptr = new T(std::move(t));
}
};
There's no reason for that to fail in that code. ptr
will point to an existing int
object created by the previous calls to bar
and the third overload will just assign the new value to that object.
However, if you did this instead:
int main() {
Foo<int> foo;
int a = 3;
const int b = 3;
foo.bar(three()); // <--- UB
return 0;
}
That foo.bar(three());
line would have undefined behaviour (which does not imply any exception), because ptr
would not be a valid pointer to an int
object.
Assuming that you only called foo.bar(three());
without the other two calls:
Why did you think that'd work? Your code is essentially equivalent to this:
int * p;
*p = 3;
That's undefined behaviour, because p
isn't pointing to a valid variable of type int
.
The "unsafe"thing, here is that, before assigning to ptr a new object, you should worry about the destiny of what ptr actually points to.
foo.bar(three());
is unsafe in the sense that you have to grant -before calling it- that ptr actually point to something. In your case it points to what was created by foo.bar(b);
But foobar(b)
makes ptr
to point to a new object forgetting the one created by foobar(a)
A more proper code can be
template<class T>
class Foo
{
T* p;
public:
Foo() :p() {}
~Foo() { delete p; }
void bar(T& t) { delete p; ptr = new T(t); }
void bar(const T& t) { delete p; ptr = new T(t); }
void bar(T&& t)
{
if(!ptr) ptr = new T(std::move(t));
else (*ptr) = std::move(t);
}
}
;
精彩评论