Bér*_*ger 11 c++ rvalue-reference unique-ptr c++11
假设我有以下代码:
class B { /* */ };
class A {
vector<B*> vb;
public:
void add(B* b) { vb.push_back(b); }
};
int main() {
A a;
B* b(new B());
a.add(b);
}
Run Code Online (Sandbox Code Playgroud)
假设在这种情况下,B*可以处理所有原始指针unique_ptr<B>.
令人惊讶的是,我无法找到如何使用转换此代码unique_ptr.经过几次尝试,我想出了以下代码,它编译:
class A {
vector<unique_ptr<B>> vb;
public:
void add(unique_ptr<B> b) { vb.push_back(move(b)); }
};
int main() {
A a;
unique_ptr<B> b(new B());
a.add(move(b));
}
Run Code Online (Sandbox Code Playgroud)
所以我的简单问题是:这是实现它的方式,特别是,这是move(b)唯一的方法吗?(我在想rvalue引用,但我不完全理解它们.)
如果你有一个完整的移动语义解释链接unique_ptr,我无法找到,请不要犹豫,分享它.
编辑根据http://thbecker.net/articles/rvalue_references/section_01.html,我的代码似乎没问题.
实际上,std :: move只是语法糖.对于类X的对象x,move(x)与以下内容相同:
static_cast <X&&>(x)
Run Code Online (Sandbox Code Playgroud)
需要这两个移动函数,因为转换为右值引用:
push_back使用B的默认移动构造函数很显然,我并不需要第二次std::move在我main(),如果我改变我的"添加"功能,通过引用(普通左值裁判)来传递.
我想要对这一切进行一些确认,不过......
我有点惊讶的是,这里没有非常清楚明确地回答,也没有在我轻易偶然发现的任何地方.虽然我对这些东西很陌生,但我认为可以说以下内容.
这种情况是一个调用函数,它构建一个unique_ptr<T>值(可能通过调用结果来转换new),并希望将它传递给某个函数,该函数将获取所指向对象的所有权(例如,通过将其存储在数据结构中,就像在这里发生的a vector).为了表明调用者已获得所有权,并且已准备放弃它,传递unique_ptr<T>值已到位.我可以看到三种传递这种价值的合理模式.
add(unique_ptr<B> b)问题所示.const左值参考传递,如add(unique_ptr<B>& b)add(unique_ptr<B>&& b)通过const左值引用传递是不合理的,因为它不允许被调用函数获取所有权(并且constrvalue引用将比那更愚蠢;我甚至不确定它是否被允许).
就有效代码而言,选项1和3几乎是等价的:它们强制调用者将rvalue写为调用的参数,可能是通过在调用中包装变量std::move(如果参数已经是rvalue,即未命名)如在结果中的投射new,这是没有必要的).但是在选项2中,不允许传递rvalue(可能来自std::move),并且必须使用命名变量调用该函数(当传递强制转换时,必须首先分配给变量).unique_ptr<T>new
当std::move确实被使用时,变量保持unique_ptr<T>在呼叫者值在概念上被解除引用(转换为右值,分别投射到右值引用),和所有权在这一点上放弃.在选项1中,解除引用是真实的,并且该值被移动到传递给被调用函数的临时值(如果calles函数将检查调用者中的变量,它将发现它已经存在空指针).所有权已经转移,调用者无法决定不接受它(对参数不做任何操作会导致在函数出口处销毁指向的值; release在参数上调用方法会阻止这种情况,但只会导致内存泄漏).令人惊讶的是,选项2和3. 在函数调用期间在语义上是等效的,尽管它们需要调用者使用不同的语法.如果被调用函数将参数传递给另一个采用右值的函数(例如push_back方法),则std::move必须在两种情况下插入,这将在该点转移所有权.如果被调用的函数忘记对参数做任何事情,那么调用者将发现自己仍然拥有该对象,如果为其保留一个名称(如选项2中的强制性); 尽管如此,在案例3中,由于函数原型要求调用者同意释放所有权(通过调用std::move或提供临时).总之,这些方法可以
const参考资料)放弃; 然而,这不是明确的(没有std::move要求甚至被允许的召唤),也没有带走所有权保证.我认为这种方法的意图相当不明确,除非明确意图取得所有权与否取决于被调用的功能(可以想象一些用途,但是呼叫者需要注意)备选方案3的意图相当明确; 提供所有权实际上是对我来说是最好的解决方案.它的效率略高于1,因为没有指针值移动到临时值(调用std::move实际上只是强制转换并且不需要任何费用); 如果指针在实际移动内容之前通过多个中间函数,则这可能尤其重要.
这是一些要试验的代码.
class B
{
unsigned long val;
public:
B(const unsigned long& x) : val(x)
{ std::cout << "storing " << x << std::endl;}
~B() { std::cout << "dropping " << val << std::endl;}
};
typedef std::unique_ptr<B> B_ptr;
class A {
std::vector<B_ptr> vb;
public:
void add(B_ptr&& b)
{ vb.push_back(std::move(b)); } // or even better use emplace_back
};
void f() {
A a;
B_ptr b(new B(123)),c;
a.add(std::move(b));
std::cout << "---" <<std::endl;
a.add(B_ptr(new B(4567))); // unnamed argument does not need std::move
}
Run Code Online (Sandbox Code Playgroud)
如上所述,输出是
storing 123
---
storing 4567
dropping 123
dropping 4567
Run Code Online (Sandbox Code Playgroud)
请注意,值在存储在向量中的有序中被销毁.尝试更改方法的原型add(如果需要,可以调整其他代码以使其编译),以及它是否实际传递其参数b.可以获得输出线的几种排列.