考虑以下代码:
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;
}
我的问题是,为什么第三次重载Foo::bar(T&& t)
会导致程序崩溃?到底发生了什么?函数返回后参数t
是否会被销毁?
此外,我们假设模板参数T
是一个非常大的对象,具有非常昂贵的复制构造函数。有没有办法使用RValue引用将其分配给Foo::ptr
而无需直接访问此指针并制作副本?
答案 0 :(得分:3)
在这一行中
void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
你可以取消引用一个未初始化的指针。这是未定义的行为。
您必须首先调用另一个版本的bar之一,因为您需要为对象创建内存
所以我会做ptr = new T(std::move(t));
如果您的类型T支持移动,则将调用移动构造函数。
<强>更新强>
我会建议这样的事情。不确定你是否需要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
};
这可以避免内存泄漏,这对您的方法来说也很容易 如果你真的需要你的类foo中的指针那么:
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));
}
};
答案 1 :(得分:0)
假设您仅在没有其他两个电话的情况下调用foo.bar(three());
:
为什么你认为这有用?您的代码基本上与此相同:
int * p;
*p = 3;
这是未定义的行为,因为p
未指向int
类型的有效变量。
答案 2 :(得分:0)
没有理由在该代码中失败。 ptr
将指向先前调用int
创建的现有bar
对象,第三个重载只会将新值分配给该对象。
但是,如果你这样做了:
int main() {
Foo<int> foo;
int a = 3;
const int b = 3;
foo.bar(three()); // <--- UB
return 0;
}
foo.bar(three());
行会有未定义的行为(这并不意味着任何异常),因为ptr
不是指向int
对象的有效指针。
答案 3 :(得分:0)
“不安全”的事情,就是在给ptr分配一个新对象之前,你应该担心ptr实际指向的命运。
foo.bar(three());
在某种意义上是不安全的,你必须在调用之前授予它 - ptr实际上指向了什么。在您的情况下,它指向foo.bar(b);
但foobar(b)
使ptr
指向一个新对象,忘记了foobar(a)
更合适的代码可以是
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);
}
}