何时通过引用传递(l值和r值)优先传递值? [英] When is overloading pass by reference (l-value and r-value) preferred to pass-by-value?

查看:189
本文介绍了何时通过引用传递(l值和r值)优先传递值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经看到它说一个 operator = 被写为采用相同类型的值的参数同时作为复制赋值操作符和移动赋值操作符在C + +11:

  Foo& operator =(Foo f)
{
swap(f);
return * this;
}

其中替代项将是许多行的两倍多重复和潜在错误:

  Foo& operator =(const Foo& f)
{
Foo f2(f);
swap(f2);
return * this;
}

Foo& operator =(Foo&& f)
{
Foo f2(std :: move(f));
swap(f2);
return * this;
}

在什么情况下ref-to-const和r-到
通过值,或什么时候有必要?我正在考虑 std :: vector :: push_back
例如定义为两个重载:

  void push_back(const value_type& val); 
void push_back(value_type&& val);

在第一个例子中,传递值用作复制赋值
运算符,移动赋值运算符
,不能 push_back
中定义标准为单个函数?

  void push_back(value_type val); 


解决方案

对于复制分配操作符可以回收资源的类型,与复制几乎从来不是实现复制赋值运算符的最佳方式。例如看看 std :: vector



这个类管理一个动态大小的缓冲区, c $ c> capacity (缓冲区可以容纳的最大长度)和 size (当前长度)。如果向量复制赋值操作符被实现 swap ,那么无论如何,但是,如果 lhs.capacity(),那么我们可以使用这个方法:code> rhs.size()!= 0 > = rhs.size(),根本不需要分配新的缓冲区。可以简单地从 rhs 分配/构造元素到 lhs 。当元素类型是可简单复制时,这可以归结为 memcpy



相同的问题std :: string / code>。



MyType MyType c $ c>有数据成员 std :: vector 和/或 std :: string



只有2次你想考虑用swap实现副本分配:


  1. p>你知道 swap 方法(包括当rhs是左值时的强制性拷贝构造)不会是非常低效的。


  2. 您知道您将总是需要复制赋值运算符具有强异常安全保证。


如果你不确定2,换句话说,你认为复制赋值运算符有时需要强异常安全保证,不要实现赋值在交换。如果您提供以下服务之一,您的客户很容易达到同样的保证:


  1. 一个noexcept交换。

  2. 一个noexcept移动赋值运算符。

例如:

  template< class T> 
T&
strong_assign(T& x,T y)
{
using std :: swap;
swap(x,y);
return x;
}

或:

  template< class T> 
T&
strong_assign(T& x,T y)
{
x = std :: move(y);
return x;
}

现在将有一些类型,其中实现副本分配与交换将是有意义的。



上:

  void push_back(const value_type& val); 
void push_back(value_type&& val);

Imagine vector< big_legacy_type> 其中: / p>

  class big_legacy_type 
{
public:
big_legacy_type(const big_legacy_type&); // expensive
// no move members ...
};

如果我们只有:

  void push_back(value_type val); 

然后 push_back $ c> big_legacy_type 插入向量将需要2份而不是1份,即使 capacity 足够了。这将是一个灾难,性能明智。



更新



HelloWorld你应该能够在任何C ++ 11符合平台上运行:

  #include< vector& 
#include< random>
#include< chrono>
#include< iostream>

class X
{
std :: vector< int> v_;
public:
explicit X(unsigned s):v_(s){}

#if SLOW_DOWN
X(const X&
X(X&&)= default;
X& operator =(X x)
{
v_.swap(x.v_);
return * this;
}
#endif
};

std :: mt19937_64 eng;
std :: uniform_int_distribution< unsigned> size(0,1000);

std :: chrono :: high_resolution_clock :: duration
test(X& x,const X& y)
{
auto t0 = std :: chrono: :high_resolution_clock :: now();
x = y;
auto t1 = std :: chrono :: high_resolution_clock :: now();
return t1-t0;
}

int
main()
{
const int N = 1000000;
typedef std :: chrono :: duration< double,std :: nano>纳米
nano ns(0);
for(int i = 0; i {
X x1(size(eng));
X x2(size(eng));
ns + = test(x1,x2);
}
ns / = N;
std :: cout<< ns.count()<< ns\\\
;
}

我编码了 X 的复制赋值运算符有两种方式:


  1. 隐含地,这等效于调用向量的复制赋值运算符。

  2. 使用复制/交换惯例,建议在 SLOW_DOWN 我想到命名它 SLEEP_FOR_AWHILE ,但这种方法实际上比睡眠语句更糟糕,如果你在电池供电的设备。



    1. 测试在0和1000之间构造一些随机大小的向量< int> s,并赋值一百万次。它每个时间,总和时间,然后找到浮点纳秒的平均时间,并打印出来。如果对高分辨率时钟的两个连续调用不返回小于100纳秒的时间,您可能需要提高向量的长度。



      这里是我的结果:

        $ clang ++ -std = c ++ 11 -stdlib = libc ++ -O3 test.cpp 
      $ a.out
      428.348ns
      $ a.out
      438.5ns
      $ a.out
      431.465ns
      $ clang ++ -std = c ++ 11 -stdlib = libc ++ -O3 -DSLOW_DOWN test.cpp
      $ a.out
      617.045ns
      $ a.out
      616.964ns
      $ a.out
      618.808ns

      通过这个简单的测试,我看到复制/ YMMV。



      上述测试平均在lhs的一半时间内有足够的容量。如果我们把这个变为极端:


      1. lhs在所有时间都有足够的容量。

      2. lhs没有足够的容量。

      那么默认副本分配对复制/交换惯用法的性能优势从560%至0%。复制/交换惯用法永远不会更快,并且可以显着减慢(对于此测试)。



      想要速度?测量。


      I have seen it said that a operator= written to take a parameter of the same type by-value serves as both copy assignment operator and move assignment operator in C++11:

      Foo& operator=(Foo f)
      {
          swap(f);
          return *this;
      }
      

      Where the alternative would be more than twice as many lines with a lot of code repetition, and potential for error:

      Foo& operator=(const Foo& f)
      {
          Foo f2(f);
          swap(f2);
          return *this;
      }
      
      Foo& operator=(Foo&& f)
      {
          Foo f2(std::move(f));
          swap(f2);
          return *this;
      }
      

      In what circumstances is the ref-to-const and r-value overload preferable to pass by value, or when is it necessary? I'm thinking about std::vector::push_back, for example which is defined as two overloads:

      void push_back (const value_type& val);
      void push_back (value_type&& val);
      

      Following the first example where pass by value serves as copy assignment operator and move assignment operator, couldn't push_back be defined in the Standard to be a single function?

      void push_back (value_type val);
      

      解决方案

      For types whose copy assignment operator can recycle resources, swapping with a copy is almost never the best way to implement the copy assignment operator. For example look at std::vector:

      This class manages a dynamically sized buffer and maintains both a capacity (maximum length the buffer can hold), and a size (the current length). If the vector copy assignment operator is implemented swap, then no matter what, a new buffer is always allocated if the rhs.size() != 0.

      However, if lhs.capacity() >= rhs.size(), no new buffer need be allocated at all. One can simply assign/construct the elements from rhs to lhs. When the element type is trivially copyable, this may boil down to nothing but memcpy. This can be much, much faster than allocating and deallocating a buffer.

      Same issue for std::string.

      Same issue for MyType when MyType has data members that are std::vector and/or std::string.

      There are only 2 times you want to consider implementing copy assignment with swap:

      1. You know that the swap method (including the obligatory copy construction when the rhs is an lvalue) will not be terribly inefficient.

      2. You know that you will always need the copy assignment operator to have the strong exception safety guarantee.

      If you're not sure about 2, in other words you think the copy assignment operator might sometimes need the strong exception safety guarantee, don't implement assignment in terms of swap. It is easy for your clients to achieve the same guarantee if you provide one of:

      1. A noexcept swap.
      2. A noexcept move assignment operator.

      For example:

      template <class T>
      T&
      strong_assign(T& x, T y)
      {
          using std::swap;
          swap(x, y);
          return x;
      }
      

      or:

      template <class T>
      T&
      strong_assign(T& x, T y)
      {
          x = std::move(y);
          return x;
      }
      

      Now there will be some types where implementing copy assignment with swap will make sense. However these types will be the exception, not the rule.

      On:

      void push_back(const value_type& val);
      void push_back(value_type&& val);
      

      Imagine vector<big_legacy_type> where:

      class big_legacy_type
      {
       public:
            big_legacy_type(const big_legacy_type&);  // expensive
            // no move members ...
      };
      

      If we had only:

      void push_back(value_type val);
      

      Then push_backing an lvalue big_legacy_type into a vector would require 2 copies instead of 1, even when capacity was sufficient. That would be a disaster, performance wise.

      Update

      Here is a HelloWorld that you should be able to run on any C++11 conforming platform:

      #include <vector>
      #include <random>
      #include <chrono>
      #include <iostream>
      
      class X
      {
          std::vector<int> v_;
      public:
          explicit X(unsigned s) : v_(s) {}
      
      #if SLOW_DOWN
          X(const X&) = default;
          X(X&&) = default;
          X& operator=(X x)
          {
              v_.swap(x.v_);
              return *this;
          }
      #endif
      };
      
      std::mt19937_64 eng;
      std::uniform_int_distribution<unsigned> size(0, 1000);
      
      std::chrono::high_resolution_clock::duration
      test(X& x, const X& y)
      {
          auto t0 = std::chrono::high_resolution_clock::now();
          x = y;
          auto t1 = std::chrono::high_resolution_clock::now();
          return t1-t0;
      }
      
      int
      main()
      {
          const int N = 1000000;
          typedef std::chrono::duration<double, std::nano> nano;
          nano ns(0);
          for (int i = 0; i < N; ++i)
          {
              X x1(size(eng));
              X x2(size(eng));
              ns += test(x1, x2);
          }
          ns /= N;
          std::cout << ns.count() << "ns\n";
      }
      

      I've coded X's copy assignment operator two ways:

      1. Implicitly, which is equivalent to calling vector's copy assignment operator.
      2. With the copy/swap idiom, suggestively under the macro SLOW_DOWN. I thought about naming it SLEEP_FOR_AWHILE, but this way is actually much worse than sleep statements if you're on a battery powered device.

      The test constructs some randomly sized vector<int>s between 0 and 1000, and assigns them a million times. It times each one, sums the times, and then finds the average time in floating point nanoseconds and prints that out. If two consecutive calls to your high resolution clock doesn't return something less than 100 nanoseconds, you may want to raise the length of the vectors.

      Here are my results:

      $ clang++ -std=c++11 -stdlib=libc++ -O3 test.cpp
      $ a.out
      428.348ns
      $ a.out
      438.5ns
      $ a.out
      431.465ns
      $ clang++ -std=c++11 -stdlib=libc++ -O3 -DSLOW_DOWN test.cpp
      $ a.out
      617.045ns
      $ a.out
      616.964ns
      $ a.out
      618.808ns
      

      I'm seeing a 43% performance hit for the copy/swap idiom with this simple test. YMMV.

      The above test, on average, has sufficient capacity on the lhs half the time. If we take this to either extreme:

      1. lhs has sufficient capacity all of the time.
      2. lhs has sufficient capacity none of the time.

      then the performance advantage of the default copy assignment over the copy/swap idiom varies from about 560% to 0%. The copy/swap idiom is never faster, and can be dramatically slower (for this test).

      Want Speed? Measure.

      这篇关于何时通过引用传递(l值和r值)优先传递值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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