【问题标题】:Can I mark a classes move-operation noexcept if it contains a standard container?如果它包含标准容器,我可以标记一个类移动操作 noexcept 吗?
【发布时间】:2014-02-14 17:20:02
【问题描述】:

在具有标准容器成员的类上实现移动操作的惯用方式不能是 noexcept,因此不能通过像 vector.push_back() 这样的操作移动。还是我弄错了?

获取速度

vector<Elem> data;
// ...
data.push_back( elem );

鼓励我们进行移动操作noexcept——因此在向量调整大小期间,库可以安全地将元素移动到重新分配的存储空间。

class Elem {
    // ...
    Elem(Elem&&) noexcept;            // noexcept important for move
    Elem& operator=(Elem&&) noexcept; // noexcept important for move
};

到目前为止一切顺利,现在我的elems 可以更快地被推回。

但是:如果我添加一个容器作为成员,我的类是否仍可以标记为 noexcept-move?所有标准容器都没有有自己的举动noexcept

class Stuff {
    vector<int> bulk;
    // ...
    Stuff(Stuff&& o)  // !!! no noexcept because of vector-move  
      : bulk(move(o.bulk))
      {}
    Stuff& operator=(Stuff&&) // !!! no noexcept...
      { /* appropriate implementation */ }
};

这也意味着,我们也不能依赖编译器生成的移动操作,对吧?下面的完整类也没有noexcept-move-operations,因此不是“快”的,对吗?

struct Holder {
    vector<int> bulk;
};

也许vector&lt;int&gt; 移动起来有点太简单了,但是vector&lt;Elem&gt; 呢?

这将对所有以容器为成员的数据结构产生重大影响。

【问题讨论】:

  • 部分问题是分配器特征的未知数。你会认为std::vector::swap() 也可能是noexcept
  • 即使标准容器的成员函数没有必需的 noexcept 规范,它们也有异常保证。如果这些保证表明操作不会抛出,您可以“安全地”自己移动 ctor noexcept。

标签: c++ c++11 containers move-semantics noexcept


【解决方案1】:

我真的感受到了你的痛苦。

一些 std::implementations 会将容器的移动成员标记为noexcept,至少以分配器属性为条件,作为扩展。您可以调整您的代码以自动利用这些扩展,例如:

class Stuff {
    std::vector<int> bulk;
    // ...
public:
    Stuff(Stuff&& o)
      noexcept(std::is_nothrow_move_constructible<std::vector<int>>::value)
      : bulk(std::move(o.bulk))
      {}
    Stuff& operator=(Stuff&&)
      noexcept(std::is_nothrow_move_assignable<std::vector<int>>::value)
      { /* appropriate implementation */ }
};

你甚至可以测试你的类型是否有 noexcept 移动成员:

static_assert(std::is_nothrow_move_constructible<Stuff>::value,
                     "I hope Stuff has noexcept move members");
static_assert(std::is_nothrow_move_assignable<Stuff>::value,
                     "I hope Stuff has noexcept move members");

特别是libc++ 的所有容器都具有 noexcept 移动成员,只要分配器允许,std::allocator 总是允许容器移动成员为 noexcept。

【讨论】:

  • 好的,谢谢。那么编译器为struct Holder; 生成的内容呢?我认为那个班级会有一个低效的举动,即。一个复制的?我想编译器不会包含条件noexcept。 (如果我继续 Scott 的笑话,我会问是否有可下载的 Windows 二进制文件。但我不会;-)
  • 实际上,Holdernoexcept 规范将与我明确建议您为 Stuff 所做的完全一样。事实上,您也可以只使用= default。您可以按照我为Stuff 展示的完全相同的方式测试Holdernoexcept 规范。具体来说,如果vector&lt;int&gt; 的移动构造函数是noexcept,那么Holder 的隐式移动构造函数也将是noexcept
  • 我松了一口气。这就是§12.9 用它的很多词陈述的,我收集。
  • @HowardHinnant 该测试可能有点误导,因为它还可以捕获noexcept 复制构造函数;见 cmets stackoverflow.com/a/18654089/1046167
  • 如果要使用复制构造函数进行移动,is_nothrow_move_ 特征仍然是提出问题的正确工具(根据设计)。
【解决方案2】:

这取决于您的标准容器使用的分配器。默认分配器std::allocator&lt;T&gt;保证不会在复制时抛出(并且没有移动构造函数),这反过来意味着容器不会在移动时抛出。

与已弃用的throw() 相比,noexcept 的一个有趣特性是您可以使用在编译时计算的表达式。但是,要测试的确切条件可能并不简单......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多