最好的方式写一个类的构造函数在C ++ 11中持有STL容器 [英] Best way to write constructor of a class who holds a STL container in C++11

查看:154
本文介绍了最好的方式写一个类的构造函数在C ++ 11中持有STL容器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  class Foo {
std :: vector< SomeType>数据_;
};

Foo 一个 std :: vector< SomeType> 对象的副本(技术上我的意思是一个副本或移动)。为 Foo



编写构造函数的最好方法是什么?我的第一个感觉是

  Foo(std :: vector< SomeType> data)noexcept:data_(std :: move(data)){}; 

使用它,构造一个实例需要0或1倍的向量拷贝,这取决于参数对于{data}是可移动的或不可移动的。

解决方案

严格来说,这不是最优的。



说明:

  Foo(std :: vector< SomeType> data)noexcept:data_(std :: move(data)){}; 

当客户端传递一个左值 std :: vector< SomeType> / code> 1复制将绑定到数据参数。然后,将移动1次将参数复制到 data _



xvalue std :: vector< SomeType> 1移动将绑定到数据参数。然后,将另一个动作将参数复制到 data _



prvalue std :: vector< SomeType> 移动将在绑定到数据参数中省略。然后1次移动将把参数复制到 data _



摘要:

 客户机参数复制数量移动数量
lvalue 1 1
xvalue 0 2
prvalue 0 1

如果您改为:

  Foo(const std :: vector< SomeType>& data):data_(data){}; 
Foo(std :: vector< SomeType>&& amp; data)noexcept:data_(std :: move(data)){};

那么你的性能稍微高一些:



当客户端通过一个左值 std :: vector< SomeType> 1将复制参数到 data _



当客户端传递xvalue std :: vector< SomeType>



当客户端传递一个prvalue std :: vector< SomeType> 1将移动将参数复制到 data _



摘要:

 客户参数份数移动数
lvalue 1 0
xvalue 0 1
prvalue 0 1

结论:



std :: vector 移动结构非常便宜,

当客户端传递一个左值时,第一个解决方案会花费额外的一个动作。这可能是在噪音水平,相比于必须分配内存的副本的成本。



第一个解决方案将花费你额外的行动,当客户通过在xvalue中。这可能是解决方案中的一个弱点,因为它使成本增加了一倍。性能测试是确保这是或不是问题的唯一可靠的方法。



客户端通过prvalue时,两个解决方案是等价的。






随着构造函数中参数的数量增加,第二个解决方案的维护成本呈指数增长。这就是你需要每个参数的const lvalue和rvalue的每个组合。这在1个参数(两个构造函数)中是非常容易控制的,在2个参数(4个构造函数)时很少,并且在那之后(8个具有3个参数的构造函数)迅速变得不可管理。因此,最佳的性能不是这里唯一关注的问题。



如果有很多参数,并且关心对于左值和x值参数的额外移动结构的成本,其他解决方案,但他们涉及相对丑陋的模板元编程技术,许多人认为太难以使用(我不,但我试图是无偏见的)。



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


class Foo {
  std::vector<SomeType> data_;
};

Say Foo can only be constructed by make a copy (technically I mean a copy or move) of a std::vector<SomeType> object. What's the best way to write constructor(s) for Foo?

My first feeling is

Foo(std::vector<SomeType> data) noexcept : data_(std::move(data)) {};

Using it, construction of an instance takes 0 or 1 times of vector copy, depending on whether the argument for {data} is moveable or not.

解决方案

Your first feeling is good. Strictly speaking it is not optimal. But it is so close to optimal that you would be justified in saying you don't care.

Explanation:

Foo(std::vector<SomeType> data) noexcept : data_(std::move(data)) {};

When the client passes in an lvalue std::vector<SomeType> 1 copy will be made to bind to the data argument. And then 1 move will be made to "copy" the argument into data_.

When the client passes in an xvalue std::vector<SomeType> 1 move will be made to bind to the data argument. And then another move will be made to "copy" the argument into data_.

When the client passes in a prvalue std::vector<SomeType> the move will be elided in binding to the data argument. And then 1 move will be made to "copy" the argument into data_.

Summary:

client argument    number of copies     number of moves
  lvalue                  1                   1
  xvalue                  0                   2
  prvalue                 0                   1

If you instead did:

Foo(const std::vector<SomeType>&  data)          : data_(data) {};
Foo(      std::vector<SomeType>&& data) noexcept : data_(std::move(data)) {};

Then you have a very slightly higher performance:

When the client passes in an lvalue std::vector<SomeType> 1 copy will be made to copy the argument into data_.

When the client passes in an xvalue std::vector<SomeType> 1 move will be made to "copy" the argument into data_.

When the client passes in a prvalue std::vector<SomeType> 1 move will be made to "copy" the argument into data_.

Summary:

client argument    number of copies     number of moves
  lvalue                  1                   0
  xvalue                  0                   1
  prvalue                 0                   1

Conclusion:

std::vector move constructions are very cheap, especially measured with respect to copies.

The first solution will cost you an extra move when the client passes in an lvalue. This is likely to be in the noise level, compared to the cost of the copy which must allocate memory.

The first solution will cost you an extra move when the client passes in an xvalue. This could be a weakness in the solution, as it doubles the cost. Performance testing is the only reliable way to assure that either this is, or is not an issue.

Both solutions are equivalent when the client passes a prvalue.


As the number of parameters in the constructor increases, the maintenance cost of the second solution increases exponentially. That is you need every combination of const lvalue and rvalue for each parameters. This is very manageable at 1 parameter (two constructors), less so at 2 parameters (4 constructors), and rapidly becomes unmanageable after that (8 constructors with 3 parameters). So optimal performance is not the only concern here.

If one has many parameters, and is concerned about the cost of an extra move construction for lvalue and xvalue arguments, there are other solutions, but they involve relatively ugly template meta-programming techniques which many consider too ugly to use (I don't, but I'm trying to be unbiased).

For std::vector, the cost of an extra move construction is typically small enough you won't be able to measure it in overall application performance.

这篇关于最好的方式写一个类的构造函数在C ++ 11中持有STL容器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆