unique_ptr 的移动语义
Moving semantics of unique_ptr
让我们考虑以下代码:
template<typename T>
void f(std::unique_ptr<T>&& uptr) { /*...*/ }
在另一个函数中:
void g()
{
std::unique_ptr<ANY_TYPE> u_ptr = std::make_unique<ANY_TYPE>();
f(std::move(u_ptr));
X: u_ptr->do_sth(); // it works, I don't understand why. Details below.
}
我不明白为什么 u_ptr
行 X
还活着。
毕竟我强迫他移动(std::move)。
---EDIT---
Ok, so now:
The code is still working:
class T{
public:
T(){}
void show(){
std::cout << "HEJ!\n";
}
};
void f(std::unique_ptr<T> ref){
ref->show();
}
int main()
{
std::unique_ptr<T> my;
my->show();
f(std::move(my));
my->show(); // How is it possible. Now, f takes unique_ptr by value
return 0;
}
您没有向我们展示运行 f
的代码,但大概它没有移动指针,即使它有权限。
您通过了 unique_ptr
参考。如果函数调用真的移动了它,那么函数就不能使用它,因为它会在函数有机会使用之前就消失了。
如果你想让函数调用真正移动指针,你需要按值传递指针,而不是引用。该值将是 unique_ptr
以便将其移入。在这种情况下,您应该将函数声明为采用 std::unique_ptr<T>
而不是 std::unique_ptr<T>&&
。然后你就可以在调用函数的时候实际调用移动构造函数了。
更新:根据您的最新更改,由于移动构造,unique_ptr
将不再引用任何有效对象。你只是永远不会检查它确实如此。调用一个不访问任何成员变量的非虚拟方法,无论对象是有效的还是被销毁的,都可以起到同样的作用,因为它不需要对象的任何东西。你也从来没有让 unique_ptr
实际上指向任何东西。
相反,让 unique_ptr
指向某物。移动后,尝试调用虚函数或访问其值被析构函数更改的成员。像这样:
#include <iostream>
#include <memory>
class T{
public:
T() : valid (true) {}
~T() { valid = false; }
bool valid;
void show(){
std::cout << "HEJ! " << valid << std::endl;
}
};
void f(std::unique_ptr<T> ref){
ref->show();
}
int main()
{
std::unique_ptr<T> my (new T); // Make it point to a new object
my->show();
f(std::move(my));
my->show(); // Try to access
return 0;
}
你对 show
的调用没有崩溃的原因是因为它没有使用 this
指针(它不会尝试修改或访问数据成员)。
试试这个:
class T{
public:
int v;
T(){}
void show(){
v = 0;
std::cout << "HEJ!\n";
}
};
void f(std::unique_ptr&& ref)
这是您最初让 f
函数采用右值引用 &&.
时的答案
您的函数采用右值 reference。因此,还没有创建新的 unique_ptr
对象,您只是传递一个 reference
.
在您的 f
函数中,如果您使用参数 uptr
创建一个本地 unique_ptr
,那么最终 uptr
将被移动以创建该新对象。
template<typename T>
void f(std::unique_ptr<T>&& uptr)
{
//move uptr into local_unique_ptr
//note that we have to use move again
//because uptr has a name, therefore its a lvalue.
auto local_unique_ptr = std::unique_ptr<T>(std::move(uptr));
}
永远要知道的重要一点是 std::move
只是一个 static_cast
。
如果您将 lvalue
传递给 std::move
,它会 returns 传递 rvalue
。如果你传一个rvalue
,它returns一个rvalue
。就是这样。
您的函数 f
实际上可能不会移动指针。仅通过 &&
获取对象不会修改对象。
u_ptr->do_sth()
可能会调用静态成员函数或不访问对象的成员函数 (this
),这就是它不会崩溃的原因。
行中的 f(std::unique_ptr<T>&& uptr)
uptr
不是一个对象 - 它是一个引用。一个能够捕捉临时对象并改变它们的引用。
这就像在问下一个例子为什么不克隆对象
void func(std::string& str);
std::string str_ = "yyy";
func(str_);
str_
由 "regular" 引用传递并且不会被复制 - 这就是通过引用传递的意思。
std::move
只是将左值转为右值引用,f(std::unique_ptr<T>&& uptr)
中的uptr
可以引用,是引用对象的引用。与通常的概念相反,std::move
不会自己进行任何移动,只会将对象转换为移动 constructor/assg 的右值引用。运营商介入。
这里,指针仍然保存着有效数据,因为它没有被移动,只是被转换为 r-value-reference。
如果你想让对象移动,你必须将参数声明为对象,而不是引用:f(std::unique_ptr<T> uptr)
在你的编辑中,你有不守规矩的行为,所以一切都可能发生。
让我们考虑以下代码:
template<typename T>
void f(std::unique_ptr<T>&& uptr) { /*...*/ }
在另一个函数中:
void g()
{
std::unique_ptr<ANY_TYPE> u_ptr = std::make_unique<ANY_TYPE>();
f(std::move(u_ptr));
X: u_ptr->do_sth(); // it works, I don't understand why. Details below.
}
我不明白为什么 u_ptr
行 X
还活着。
毕竟我强迫他移动(std::move)。
---EDIT---
Ok, so now:
The code is still working:
class T{
public:
T(){}
void show(){
std::cout << "HEJ!\n";
}
};
void f(std::unique_ptr<T> ref){
ref->show();
}
int main()
{
std::unique_ptr<T> my;
my->show();
f(std::move(my));
my->show(); // How is it possible. Now, f takes unique_ptr by value
return 0;
}
您没有向我们展示运行 f
的代码,但大概它没有移动指针,即使它有权限。
您通过了 unique_ptr
参考。如果函数调用真的移动了它,那么函数就不能使用它,因为它会在函数有机会使用之前就消失了。
如果你想让函数调用真正移动指针,你需要按值传递指针,而不是引用。该值将是 unique_ptr
以便将其移入。在这种情况下,您应该将函数声明为采用 std::unique_ptr<T>
而不是 std::unique_ptr<T>&&
。然后你就可以在调用函数的时候实际调用移动构造函数了。
更新:根据您的最新更改,由于移动构造,unique_ptr
将不再引用任何有效对象。你只是永远不会检查它确实如此。调用一个不访问任何成员变量的非虚拟方法,无论对象是有效的还是被销毁的,都可以起到同样的作用,因为它不需要对象的任何东西。你也从来没有让 unique_ptr
实际上指向任何东西。
相反,让 unique_ptr
指向某物。移动后,尝试调用虚函数或访问其值被析构函数更改的成员。像这样:
#include <iostream>
#include <memory>
class T{
public:
T() : valid (true) {}
~T() { valid = false; }
bool valid;
void show(){
std::cout << "HEJ! " << valid << std::endl;
}
};
void f(std::unique_ptr<T> ref){
ref->show();
}
int main()
{
std::unique_ptr<T> my (new T); // Make it point to a new object
my->show();
f(std::move(my));
my->show(); // Try to access
return 0;
}
你对 show
的调用没有崩溃的原因是因为它没有使用 this
指针(它不会尝试修改或访问数据成员)。
试试这个:
class T{
public:
int v;
T(){}
void show(){
v = 0;
std::cout << "HEJ!\n";
}
};
void f(std::unique_ptr&& ref)
这是您最初让 f
函数采用右值引用 &&.
您的函数采用右值 reference。因此,还没有创建新的 unique_ptr
对象,您只是传递一个 reference
.
在您的 f
函数中,如果您使用参数 uptr
创建一个本地 unique_ptr
,那么最终 uptr
将被移动以创建该新对象。
template<typename T>
void f(std::unique_ptr<T>&& uptr)
{
//move uptr into local_unique_ptr
//note that we have to use move again
//because uptr has a name, therefore its a lvalue.
auto local_unique_ptr = std::unique_ptr<T>(std::move(uptr));
}
永远要知道的重要一点是 std::move
只是一个 static_cast
。
如果您将 lvalue
传递给 std::move
,它会 returns 传递 rvalue
。如果你传一个rvalue
,它returns一个rvalue
。就是这样。
您的函数 f
实际上可能不会移动指针。仅通过 &&
获取对象不会修改对象。
u_ptr->do_sth()
可能会调用静态成员函数或不访问对象的成员函数 (this
),这就是它不会崩溃的原因。
行中的 f(std::unique_ptr<T>&& uptr)
uptr
不是一个对象 - 它是一个引用。一个能够捕捉临时对象并改变它们的引用。
这就像在问下一个例子为什么不克隆对象
void func(std::string& str);
std::string str_ = "yyy";
func(str_);
str_
由 "regular" 引用传递并且不会被复制 - 这就是通过引用传递的意思。
std::move
只是将左值转为右值引用,f(std::unique_ptr<T>&& uptr)
中的uptr
可以引用,是引用对象的引用。与通常的概念相反,std::move
不会自己进行任何移动,只会将对象转换为移动 constructor/assg 的右值引用。运营商介入。
这里,指针仍然保存着有效数据,因为它没有被移动,只是被转换为 r-value-reference。
如果你想让对象移动,你必须将参数声明为对象,而不是引用:f(std::unique_ptr<T> uptr)
在你的编辑中,你有不守规矩的行为,所以一切都可能发生。