繁体   English   中英

c ++ unique_ptr参数传递

[英]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)

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

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

显然,如果我改变我的“add”函数以通过引用传递(普通左值ref),我不需要在main()使用第二个std::move

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

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

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

  1. 按值传递,如问题中的add(unique_ptr<B> b)
  2. 通过非const左值引用传递,如add(unique_ptr<B>& b)
  3. 通过rvalue引用传递,如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或提供临时)。 总之,这些方法可以

  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
}

如上所述,输出是

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM