2013-08-18 49 views
21

我看到它说,operator=书面采取按值作为两个拷贝赋值运算符相同类型的参数,并在移动赋值运算符C++ 11:什么时候重载通过参考(l值和r值)首选通过值?

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; 
} 

在什么情况下,是裁判给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); 
+0

当对象很重要移动时(例如,包含大量数据成员),按值表单可能更昂贵(两次移动而不是一次)。 – Angew

+0

这可能部分是历史原因,因为'std :: vector'在C++ 03中已经有'T const&'重载了。可能有代码依赖于现有的超载(比如某人拿到了成员函数的地址)。还要注意,在标准中,不需要优化必须实现的代码行,因为它由编译器实现者编写一次,但几乎在其他地方使用。额外的开发成本可以忽略不计。 –

+0

出于显而易见的原因,肯定需要的一种情况是复制/移动构造函数。 – celtschk

回答

26

该类型的拷贝赋值运算符可以回收的资源,并抄送交换是几乎从来没有实现拷贝赋值运算符的最佳途径。例如看std::vector

此类管理动态大小的缓冲区,并保持二者capacity(该缓冲区可容纳最大长度),和一个size(当前长度)。如果vector复制赋值运算符实现了swap,那么无论如何,如果rhs.size() != 0为新的缓冲区总是被分配。

但是,如果lhs.capacity() >= rhs.size(),则根本不需要分配新的缓冲区。人们可以简单地分配/构建从rhslhs的元素。当元素类型可以复制时,这可能归结为memcpy。这可以比分配和释放缓冲区快得多,更多

std::string的问题相同。

同样的问题,为MyTypeMyType有数据成员是std::vector和/或std::string

只有2次,要考虑实现与交换拷贝赋值:

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

  2. 你知道你会总是需要复制赋值运算符有强大的异常安全保证。

如果你不知道2,换句话说,你想拷贝赋值运算符可能有时需要强异常安全保障,不交换方面实行分配。如果您提供以下其中一项,您的客户很容易达到相同的保证:

  1. A noexcept swap。
  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); 

想象vector<big_legacy_type>其中:

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

如果我们只有:

void push_back(value_type val); 

然后push_back荷兰国际集团的左值big_legacy_typevector需要2份代替1,甚至当capacity就足够了。这将是一场灾难,表现明智。

更新

下面是你应该能够对任何C++ 11符合平台上运行一个HelloWorld:

#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"; 
} 

我编写X的拷贝赋值运算符两种方式:

  1. 隐式地,这相当于调用vector的复制赋值运算符。
  2. 随着复制/交换成语,暗示下的宏SLOW_DOWN。我想过命名它SLEEP_FOR_AWHILE,但如果你使用电池供电的设备,这种方式实际上比睡眠声明差得多。

该测试构造了一些在0到1000之间的随机大小的vector<int>,并为它们指定了一百万次。它计算每一个,总结时间,然后以浮点纳秒为单位查找平均时间并打印出来。如果连续两次调用高分辨率时钟不会返回小于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 

我看到了这个简单的测试复制/交换成语了43%的性能损失。因人而异。

上述测试平均在一半的时间内有足够的容量。如果我们采取这种方式:

  1. lhs在所有的时间都有足够的容量。
  2. lhs在任何时候都有足够的容量。

那么默认拷贝分配在复制/交换习惯用法上的性能优势从约560%变化到0%。复制/交换习惯从不会更快,并且可能会显着更慢(对于此测试)。

想要速度?测量。

+0

复制elision帮助吗?在第二种情况下会删除* other *副本吗?我试图与http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/一致,谢谢 – cdmh

+8

当rhs是一个左值时,它并没有帮助。从技术上讲,这篇文章没有任何问题。然而它留下了错误的印象:总是按价值复制是可怕的建议。对于这个问题,任何“任何事情”都是可怕的建议。这篇文章应该给你留下这样的印象:**有时**一个按值参数可以是好的,甚至是最好的东西。但太多人忽视了“有时”,因为文章没有澄清这一点。保持工具箱中的价值传递。但是,如果没有设计,默认使用它会要求性能问题。 –

+0

@HowardHinnant我是***之一“有时”*** *意味着* ***“总是”***傻瓜。感谢您的意见。 – Manu343726