【问题标题】:Putting non-copyable objects into std-containers将不可复制的对象放入标准容器
【发布时间】:2011-08-06 22:43:47
【问题描述】:

这个类是否设计了标准的 C++0x 方法来防止 copyassign,以保护客户端代码免受 data 的意外双重删除?

struct DataHolder {
  int *data;   // dangerous resource
  DataHolder(const char* fn); // load from file or so
  DataHolder(const char* fn, size_t len); // *from answers: added*
  ~DataHolder() { delete[] data; }

  // prevent copy, to prevent double-deletion
  DataHolder(const DataHolder&) = delete;
  DataHolder& operator=(const DataHolder&) = delete;

  // enable stealing
  DataHolder(DataHolder &&other) {
    data=other.data; other.data=nullptr;
  }
  DataHolder& operator=(DataHolder &&other) {
    if(&other!=this) { data = other.data; other.data=nullptr};
    return *this;
  }
};

您注意到,我在这里定义了新的 movemove-assign 方法。我是否正确实施了它们?

我有什么方法可以 - 使用 movemove-assign 定义 - 将 DataHolder 放入标准容器中?像vector?我该怎么做?

我想知道,我想到了一些选项:

// init-list. do they copy? or do they move?
// *from answers: compile-error, init-list is const, can nor move from there*
vector<DataHolder> abc { DataHolder("a"), DataHolder("b"), DataHolder("c") };

// pushing temp-objects.
vector<DataHolder> xyz;
xyz.push_back( DataHolder("x") );
// *from answers: emplace uses perfect argument forwarding*
xyz.emplace_back( "z", 1 );

// pushing a regular object, probably copies, right?
DataHolder y("y");
xyz.push_back( y ); // *from anwers: this copies, thus compile error.*

// pushing a regular object, explicit stealing?
xyz.push_back( move(y) );

// or is this what emplace is for?
xyz.emplace_back( y ); // *from answers: works, but nonsense here*

emplace_back 的想法在这里只是一个猜测。

编辑:为了方便读者,我将答案放入示例代码中。

【问题讨论】:

  • const RValue 引用?

标签: c++ stl c++11 rvalue-reference noncopyable


【解决方案1】:

您的示例代码看起来大部分正确。

  1. const DataHolder &amp;&amp;other(在两个地方)。

  2. if(&amp;other!=this) 在您的移动赋值运算符中看起来没有必要但无害。

  3. 初始化列表向量构造函数不起作用。这将尝试复制您的DataHolder,您应该会收到编译时错误。

  4. 带有右值参数的 push_back 和 emplace_back 调用将起作用。带有左值参数的那些(使用y)会给你编译时错误。

push_back 和 emplace_back 在你使用它们的方式上确实没有区别。 emplace_back 用于当您不想在向量之外构造 DataHolder 时,而是传递参数以仅在向量内部构造一个。例如:

// Imagine this new constructor:
DataHolder(const char* fn, size_t len);

xyz.emplace_back( "data", 4 );  // ok
xyz.push_back("data", 4 );  // compile time error

更新:

我刚刚注意到您的移动赋值运算符有内存泄漏。

DataHolder& operator=(DataHolder &&other)
{
   if(&other!=this)
   {
      delete[] data;  // insert this
      data = other.data;
      other.data=nullptr;
   }
   return *this;
}

【讨论】:

  • 我不认为阻止移动到自我是无用的。毕竟,a = std::move(a) 是有效的,应该是空操作。
  • (1) 复制粘贴错误。我把它编辑了。 (2) 真的吗?这不是复制的常用模式吗?临时对象不能是this?嗯。 (3) 对,init-list 成员的行为const。好的。 (4) 啊,那是“安顿”……当然。现在我明白了。 (*) 美丽所有这些新事物一起工作:rvalue-refs、完美转发、模板可变参数。太好了!
  • @towi - 复制的常用模式是“复制和交换”成语:T&amp; operator =(T rhs /* pass by value! */) { this-&gt;swap(rhs); return *this; } 这有三个好处:1)代表复制 ctor,因此无需复制代码; 2) 是异常安全的,当且仅当交换不抛出时(而且它不应该!);和 3) 自分配工作没有明确的检查。 this-&gt;swap(rhs)(和T&amp;&amp; rhs)通常也是实现移动赋值运算符的好方法。
  • @JohannesD waitwaitwait... 好的,我可以理解的传递值,这就是复制ctor的委托发生的地方。但是T::swap(T&amp;) 是怎么回事?我必须实现它,那将是重复的代码。好的,我们通常无论如何都会实现它。这就是你的意思,对吧?为什么不需要明确的自检?因为它是在swap 完成的?嗯...因为rhs 是一个副本。但这会产生两份副本,一份在按值调用中隐含,一份在swap(T&amp;) 中逐段显式。好吧,这很便宜,我明白了!你建议两个swap impls,对吧? swap(T&amp;)swap(T&amp;&amp;)?十亿...
  • @JohannesD 我只想指出,在这个类中,我想完全阻止客户端代码使用复制。在这里复制很昂贵,data 也可能是不可复制的资源——文件、锁。我不喜欢为数据保存类提供一个 copy-ctor,那么你永远不知道你复制的频率。
【解决方案2】:

没有名称的临时对象,例如。 DataHolder("a"),可用时移动。 C++0x 中的标准容器会尽可能移动,这也允许将std::unique_ptr 放入标准容器中。
除此之外,您错误地执行了移动操作:

  // enable stealing
  DataHolder(const DataHolder &&other) {
    data=other.data; other.data=nullptr;
  }
  DataHolder& operator=(const DataHolder&&other) {
    if(&other!=this) { data = other.data; other.data=nullptr};
    return *this;
  }

如何从一个常量对象中移动?您不能只更改otherdata,因为other 是不变的。将其更改为简单的DataHolder&amp;&amp;

【讨论】:

  • ups,const&amp;&amp;ies 的复制粘贴错误。我删除了 consts,这样读者就不会知道这个错误的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-26
  • 1970-01-01
  • 2013-04-08
  • 2021-09-26
  • 2011-03-28
  • 1970-01-01
  • 2017-11-21
相关资源
最近更新 更多