[英]c++ unique_ptr argument passing
假设我有以下代码:
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);
}
假设在这种情况下,所有原始指针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));
}
所以我的简单问题是:这是做到这一点的方式,特别是move(b)
唯一的方法吗? (我在想rvalue引用,但我不完全理解它们。)
如果你有关于移动语义, unique_ptr
等的完整解释的链接,我无法找到,请不要犹豫,分享它。
编辑根据http://thbecker.net/articles/rvalue_references/section_01.html ,我的代码似乎没问题。
实际上,std :: move只是语法糖。 对于类X的对象x, move(x)
与以下内容相同:
static_cast <X&&>(x)
需要这两个移动函数,因为转换为右值引用:
push_back
使用B的默认移动构造函数 显然,如果我改变我的“add”函数以通过引用传递(普通左值ref),我不需要在main()
使用第二个std::move
。
我想要对这一切进行一些确认,不过......
我有点惊讶的是,这里没有非常清楚明确地回答,也没有在我轻易偶然发现的任何地方。 虽然我对这些东西很陌生,但我认为可以说以下内容。
这种情况是一个调用函数,它构建一个unique_ptr<T>
值(可能通过调用new
来调用结果),并希望将它传递给某个函数,该函数将获取指向的对象的所有权(通过将其存储在一个例如,数据结构,这里发生在vector
)。 为了表明调用者已获得所有权,并且已准备好放弃它,传递unique_ptr<T>
值就绪了。 我可以看到三种传递这种价值的合理模式。
add(unique_ptr<B> b)
。 const
左值引用传递,如add(unique_ptr<B>& b)
add(unique_ptr<B>&& b)
传递const
lvalue引用是不合理的,因为它不允许被调用函数获取所有权(并且const
rvalue引用将比那更愚蠢;我甚至不确定它是否被允许)。
就有效代码而言,选项1和3几乎是等价的:它们强制调用者将rvalue写为调用的参数,可能是通过在调用std::move
包装变量(如果参数已经是rvalue)即,无名如从结果铸造new
,这是没有必要的)。 但是在选项2中, 不允许传递rvalue(可能来自std::move
),并且必须使用命名的unique_ptr<T>
变量调用该函数(当从new
传递unique_ptr<T>
时,必须首先赋值给变量) )。
确实使用std::move
,在调用者中保存unique_ptr<T>
值的变量在概念上被解除引用(转换为rvalue,分别转换为rvalue引用),此时放弃所有权。 在选项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
}
如上所述,输出是
storing 123
---
storing 4567
dropping 123
dropping 4567
请注意,值在存储在向量中的有序中被销毁。 尝试更改方法add
的原型(如果需要,可以调整其他代码以使其编译),以及它是否实际传递其参数b
。 可以获得输出线的几种排列。
是的,这是应该怎么做的。 您明确将所有权从main
转移到A
这与您之前的代码基本相同,只是它更明确,更可靠。
所以我的简单问题是:这是做到这一点的方式,尤其是“移动(b)”是唯一的方法吗? (我在想rvalue参考,但我不完全理解它...)
如果你有关于移动语义的完整解释的链接,unique_ptr ......我无法找到,请不要犹豫。
无耻插头 ,搜索标题“搬入成员”。 它准确描述了您的场景。
由于C ++ 14,你在main
代码可以简化一点:
a.add( make_unique<B>() );
你可以把B
的构造函数的参数放在内括号里面。
您还可以考虑获取原始指针所有权的类成员函数:
void take(B *ptr) { vb.emplace_back(ptr); }
并且main
的相应代码将是:
a.take( new B() );
另一种选择是使用完美转发来添加矢量成员:
template<typename... Args>
void emplace(Args&&... args)
{
vb.emplace_back( std::make_unique<B>(std::forward<Args>(args)...) );
}
和主要代码:
a.emplace();
和以前一样,你可以在括号内放置B
构造函数参数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.