c++ unique_ptr 参数传递
Posted
技术标签:
【中文标题】c++ unique_ptr 参数传递【英文标题】:c++ unique_ptr argument passing 【发布时间】:2012-09-22 20:35:23 【问题描述】:假设我有以下代码:
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)
是唯一的方法吗? (我在考虑右值引用,但我不完全理解它们。)
如果你有一个我找不到的完整解释移动语义的链接,unique_ptr
等,请不要犹豫,分享它。
编辑根据http://thbecker.net/articles/rvalue_references/section_01.html,我的代码似乎没问题。
实际上,std::move 只是语法糖。对于 X 类的对象 x,move(x)
与:
static_cast <X&&>(x)
需要这 2 个移动函数,因为强制转换为右值引用:
-
防止函数“add”按值传递
让
push_back
使用B的默认移动构造函数
显然,如果我将“添加”函数更改为通过引用(普通左值引用)传递,我不需要 main()
中的第二个 std::move
。
不过,我想确认一下这一切……
【问题讨论】:
我认为这可能对你有用:***.com/questions/2876641/… @KirilKirov 谢谢,这个问题提供了有趣的链接(见编辑) 如果您以这种方式更改添加,则可以避免 main 中的std::move
,但这会很糟糕,因为 add
的调用者不知道他们是否仍然拥有该对象不看add
的来源。
@DeadMG Argh。我知道我会做些傻事。谢谢。顺便说一句,我正在考虑通过右值引用传递我的参数,即“add(B&& b)”,以抑制其实现中的“移动”,在这种情况下这似乎是多余的。但是这样做(通过右值引用而不是值传递),我得到了相同的结果,特别是,我不能抑制这个“移动”。有什么理由吗?
因为所有变量都是左值,包括那些对右值的引用。
【参考方案1】:
是的,应该这样做。您将所有权从main
明确转移到A
。这与您之前的代码基本相同,只是它更明确且更可靠。
【讨论】:
一些扩展讨论:Herb Sutter's GOTW #91 和 Scott Meyers on passing move-only types 特别是“按值”与“按右值引用”【参考方案2】:我有点惊讶,这里没有非常清楚明确地回答这个问题,也没有在我容易偶然发现的任何地方回答。虽然我对这些东西很陌生,但我认为可以说以下内容。
这种情况是一个调用函数,它构建了一个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
左值引用是不合理的,因为它不允许被调用的函数取得所有权(而const
右值引用会比这更愚蠢;我什至不确定它是否被允许)。
就有效代码而言,选项 1 和 3 几乎是等价的:它们强制调用者写入一个右值作为调用的参数,可能通过在对 std::move
的调用中包装一个变量(如果参数已经一个右值,即在new
的结果中未命名,这不是必需的)。然而,在选项 2 中,传递右值(可能来自 std::move
)是不允许,并且必须使用命名的 unique_ptr<T>
变量调用函数(当从 new
传递强制转换时,一个必须先分配给一个变量)。
当确实使用了std::move
时,调用者中持有unique_ptr<T>
值的变量在概念上被取消引用(转换为右值,分别转换为右值引用),此时放弃所有权。在选项 1 中,取消引用是真实的,并且值被移动到传递给被调用函数的临时值(如果调用函数将检查调用者中的变量,它会发现它已经持有一个空指针)。所有权已经转移,调用者无法决定不接受它(对参数不做任何事情会导致指向的值在函数退出时被销毁;在参数上调用 release
方法会阻止这种情况,但只会导致内存泄漏)。令人惊讶的是,选项 2. 和 3. 在函数调用期间语义是等效的,尽管它们需要调用者的不同语法。如果被调用函数将参数传递给另一个采用右值的函数(例如push_back
方法),则必须在这两种情况下插入std::move
,这将在该点转移所有权。如果被调用的函数忘记对参数做任何事情,那么调用者会发现自己仍然拥有该对象,如果持有它的名称(如选项 2 中的强制要求);尽管如此,在案例 3 中,由于函数原型要求调用者同意释放所有权(通过调用 std::move
或提供临时的)。总而言之,这些方法确实
-
强制调用者放弃所有权,并确保实际声明所有权。
强制调用者拥有所有权,并准备(通过提供非
const
引用)放弃它;然而,这不是明确的(不需要甚至不允许调用std::move
),也不能保证剥夺所有权。我认为这种方法的意图相当不明确,除非明确表示是否获得所有权由被调用函数自行决定(可以想象一些用途,但调用者需要注意)
强制调用者明确指出放弃所有权,如 1 所示。(但所有权的实际转移延迟到函数调用之后)。
选项 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
。可以得到输出线的几种排列。
【讨论】:
【参考方案3】:所以我的简单问题是:这是执行此操作的方法吗?特别是,“move(b)”是唯一的方法吗? (我正在考虑右值引用,但我并不完全理解它......)
如果你有一个完整解释移动语义的链接,unique_ptr...我找不到,请不要犹豫。
Shameless plug,搜索标题“Moving into members”。它准确地描述了您的场景。
【讨论】:
【参考方案4】:您在main
中的代码可以简化一点,因为 C++14:
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)...) );
以及main中的代码:
a.emplace();
和以前一样,您可以将 B
的构造函数参数放在括号内。
Link to working example
【讨论】:
以上是关于c++ unique_ptr 参数传递的主要内容,如果未能解决你的问题,请参考以下文章