C++11 编写在C+中持有STL容器的类的构造函数的最佳方法+;11

C++11 编写在C+中持有STL容器的类的构造函数的最佳方法+;11,c++11,constructor,move,C++11,Constructor,Move,使用它,实例的构造需要向量副本的0到1倍,这取决于{data}的参数是否可移动。class Foo{ Foo(std::vector<SomeType> data) noexcept : data_(std::move(data)) {}; 使用数据_t=std::vector; 数据; 公众: constexpr Foo(data_t&d)noexcept:data_(std::forward(d)){ }; 你的第一感觉很好。严格地说,这不是最优的。但它是如此接近最优,以至于你

使用它,实例的构造需要向量副本的0到1倍,这取决于{data}的参数是否可移动。

class Foo{
Foo(std::vector<SomeType> data) noexcept : data_(std::move(data)) {};
使用数据_t=std::vector; 数据; 公众: constexpr Foo(data_t&d)noexcept:data_(std::forward(d)){ };
你的第一感觉很好。严格地说,这不是最优的。但它是如此接近最优,以至于你有理由说你不在乎

说明:

class Foo {
  using data_t = std::vector<SomeType>;
  data_t data_;
public:
  constexpr Foo(data_t && d) noexcept : data_(std::forward<data_t>(d)) {}
};
client argument    number of copies     number of moves
  lvalue                  1                   1
  xvalue                  0                   2
  prvalue                 0                   1
如果您改为:

class Foo {
  using data_t = std::vector<SomeType>;
  data_t data_;
public:
  constexpr Foo(data_t && d) noexcept : data_(std::forward<data_t>(d)) {}
};
client argument    number of copies     number of moves
  lvalue                  1                   1
  xvalue                  0                   2
  prvalue                 0                   1
结论:

class Foo {
  using data_t = std::vector<SomeType>;
  data_t data_;
public:
  constexpr Foo(data_t && d) noexcept : data_(std::forward<data_t>(d)) {}
};
client argument    number of copies     number of moves
  lvalue                  1                   1
  xvalue                  0                   2
  prvalue                 0                   1
std::vector
move构造非常便宜,尤其是在拷贝方面

当客户端传入左值时,第一个解决方案将花费您额外的移动。与必须分配内存的拷贝成本相比,这可能是噪声级

当客户端传入一个xvalue时,第一个解决方案将花费您额外的移动。这可能是解决方案中的一个弱点,因为它会使成本翻倍。性能测试是确保这不是问题的唯一可靠方法

当客户机传递PRV值时,这两种解决方案是等效的


随着构造函数中参数数量的增加,第二个解决方案的维护成本呈指数增长。也就是说,您需要每个参数的const lvalue和rvalue的每个组合。在1个参数(两个构造函数)下,这是非常容易管理的,在2个参数(4个构造函数)下则不太容易管理,然后很快就变得不可管理(8个构造函数和3个参数)。因此,最佳性能不是这里唯一关心的问题

如果一个参数有很多,并且关心LValk和XValk参数的额外移动构造的成本,那么还有其他的解决方案,但是它们涉及相对难看的模板元编程技术,其中很多人认为太难于使用(我没有,但我试图保持无偏)。


对于
std::vector
,额外移动构造的成本通常很小,您无法在整体应用程序性能中衡量它。

Howard Hinnant的答案中提到的性能最佳解决方案的复杂性问题,对于采用多个参数的构造器,可以通过使用完美转发来解决:

client argument    number of copies     number of moves
  lvalue                  1                   0
  xvalue                  0                   1
  prvalue                 0                   1
模板
Foo(A0&&A0):数据(std::forward(A0)){
如果参数更多,请相应扩展:

template<typename A0>
Foo(A0 && a0) : data_(std::forward<A0>(a0)) {}
模板
Foo(A0和A0,A1和A1,…)
:m0(标准::正向(a0))
,m1(标准::正向(a1))
, ...
{}

坏了。无法使用命名向量变量调用此构造函数。-1通用引用使构造函数强制使用右值引用。只有模板函数具有T&&通用引用,其中向前表示某些内容。我认为Howard提到的一个丑陋部分与
Foo
的可构造性有关:此构造函数当被问及
是否可构造时,是否“撒谎”,因为实例化本身对于任何类型(以及重载解析)都会成功,但程序可能格式错误。当数据成员的构造是非法的时,您需要添加一些SFINAE来禁用构造。同意dyp。我建议使用
约束是可构造的
@dyp:Copy/paste错误,已修复,谢谢!从技术上讲,move版本不应该是
noexcept
,因为
vector
move构造函数在标准中没有标记
noexcept
。但实际上,我知道只有一家供应商没有将vector move构造函数标记为
noexcept
作为扩展。我对
vector
的异常安全保证相当困惑,但我认为对于默认分配器,移动构造器可能不会抛出..?@dyp:我同意你的看法。然而,根据标准
不是row\u move\u constructible::value
是false。然而,该标准也为供应商提供了余地,使
为非可构造::值
==真(即通过添加noexcept规范),并且仍然符合要求。据我所知,libc++和libstdc++有,vc++没有它不是应该向前移动而不是向前移动吗?@MaxGalkin:没错。赫伯说的是一个setter,它将根据参数做一个赋值。这里我们看的是一个构造函数,它必须从参数构造。这里有一个重要的教训:永远不要使用“复制”或“移动”这两个词将构造和赋值合并到一个性能参数中。始终分析“复制构造”或“复制分配”,而不是“复制”。对于不同的类型,您的分析可能会有所不同
vector
array
具有不同的属性,因此可以得到不同的答案。如果你的类型是泛型的,你必须保守。