c ++ unique_ptr参数传递

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)

需要这两个移动函数,因为转换为右值引用:

  1. 防止函数"add"传递值
  2. 使得push_back使用B的默认移动构造函数

很显然,我并不需要第二次std::move在我main(),如果我改变我的"添加"功能,通过引用(普通左值裁判)来传递.

我想要对这一切进行一些确认,不过......

Mar*_*wen 9

我有点惊讶的是,这里没有非常清楚明确地回答,也没有在我轻易偶然发现的任何地方.虽然我对这些东西很陌生,但我认为可以说以下内容.

这种情况是一个调用函数,它构建一个unique_ptr<T>值(可能通过调用结果来转换new),并希望将它传递给某个函数,该函数将获取所指向对象的所有权(例如,通过将其存储在数据结构中,就像在这里发生的a vector).为了表明调用者已获得所有权,并且已准备放弃它,传递unique_ptr<T>值已到位.我可以看到三种传递这种价值的合理模式.

  1. 按价值传递,如add(unique_ptr<B> b)问题所示.
  2. 通过非const左值参考传递,如add(unique_ptr<B>& b)
  3. 通过右值参考传递,如 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或提供临时).总之,这些方法可以

  1. 强制呼叫者放弃所有权,并确保实际声明它.
  2. 强制来电者拥有所有权,并做好准备(通过提供非const参考资料)放弃; 然而,这不是明确的(没有std::move要求甚至被允许的召唤),也没有带走所有权保证.我认为这种方法的意图相当不明确,除非明确意图取得所有权与否取决于被调用的功能(可以想象一些用途,但是呼叫者需要注意)
  3. 强制呼叫者明确表示放弃所有权,如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
}
Run Code Online (Sandbox Code Playgroud)

如上所述,输出是

storing 123
---
storing 4567
dropping 123
dropping 4567
Run Code Online (Sandbox Code Playgroud)

请注意,值在存储在向量中的有序中被销毁.尝试更改方法的原型add(如果需要,可以调整其他代码以使其编译),以及它是否实际传递其参数b.可以获得输出线的几种排列.


Pup*_*ppy 8

是的,这是应该怎么做的.您明确将所有权转移mainA.这与您之前的代码基本相同,只是它更明确,更可靠.