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&lt;B&gt;处理。

令人惊讶的是,我无法找到如何使用 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&lt;T&gt; 值(可能通过将调用的结果强制转换为new),并希望将它传递给某个函数,该函数将获得指向对象的所有权(通过例如,将其存储在数据结构中,就像这里发生的那样,存储在 vector) 中。为了表明调用者已获得所有权,并准备放弃它,传递一个unique_ptr&lt;T&gt; 值已经到位。据我所知,传递这样一个值的三种合理模式。

    按值传递,如问题中的add(unique_ptr&lt;B&gt; b)。 通过非const 左值引用传递,如add(unique_ptr&lt;B&gt;&amp; b) 通过右值引用传递,如add(unique_ptr&lt;B&gt;&amp;&amp; b)

传递const 左值引用是不合理的,因为它不允许被调用的函数取得所有权(而const 右值引用会比这更愚蠢;我什至不确定它是否被允许)。

就有效代码而言,选项 1 和 3 几乎是等价的:它们强制调用者写入一个右值作为调用的参数,可能通过在对 std::move 的调用中包装一个变量(如果参数已经一个右值,即在new 的结果中未命名,这不是必需的)。然而,在选项 2 中,传递右值(可能来自 std::move)是不允许,并且必须使用命名的 unique_ptr&lt;T&gt; 变量调用函数(当从 new 传递强制转换时,一个必须先分配给一个变量)。

当确实使用了std::move 时,调用者中持有unique_ptr&lt;T&gt; 值的变量在概念上被取消引用(转换为右值,分别转换为右值引用),此时放弃所有权。在选项 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 参数传递的主要内容,如果未能解决你的问题,请参考以下文章

cpp 线程传递参数

通过变量传递 unique_ptr [重复]

C++中函数参数的传递方式有哪几种

是否传递指针参数,在 C++ 中按值传递?

从函数调用传递参数或从变量传递参数之间的 C++ 区别

c++数组传递参数与返回