问题描述
我已经看到它说一个 operator=
被写成一个相同类型的按值参数在 C++11 中同时用作复制赋值运算符和移动赋值运算符:
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;
}
在什么情况下,ref-to-const 和 r-value 重载优于按值传递,或者什么时候需要?我在考虑 std::vector::push_back
,例如定义为两个重载:
In what circumstances is the ref-to-const and r-value overload preferable topass 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);
遵循第一个示例,其中按值传递 用作复制分配运算符和移动赋值运算符,不能在push_back
中定义标准是一个单一的功能?
Following the first example where pass by value serves as copy assignmentoperator and move assignment operator, couldn't push_back
be defined inthe Standard to be a single function?
void push_back (value_type val);
推荐答案
对于复制赋值运算符可以回收资源的类型,用副本交换几乎从来都不是实现复制赋值运算符的最佳方式.例如看 std::vector
:
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
:
这个类管理一个动态大小的缓冲区并维护一个容量
(缓冲区可以容纳的最大长度)和一个size
(当前长度).如果vector
复制赋值运算符被实现swap
,那么无论如何,如果rhs.size() != 0
总是分配一个新的缓冲区代码>.
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
.
然而,如果lhs.capacity() >= rhs.size()
,则根本不需要分配新的缓冲区.可以简单地分配/构造从 rhs
到 lhs
的元素.当元素类型可以简单地复制时,这可能归结为 memcpy
.这比分配和取消分配缓冲区要快得多,.
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.
std::string
同样的问题.
当 MyType
的数据成员为 std::vector
和/或 std::stringMyType
也有同样的问题/代码>.
Same issue for MyType
when MyType
has data members that are std::vector
and/or std::string
.
只有 2 次您想考虑使用交换实现复制分配:
There are only 2 times you want to consider implementing copy assignment with swap:
您知道
swap
方法(包括当 rhs 是左值时的强制复制构造)不会非常低效.
You know that the
swap
method (including the obligatory copy construction when the rhs is an lvalue) will not be terribly inefficient.
您知道您将总是需要复制赋值运算符来拥有强大的异常安全保证.
You know that you will always need the copy assignment operator to have the strong exception safety guarantee.
如果您不确定 2,换句话说,您认为复制赋值运算符可能有时需要强大的异常安全保证,请不要在交换方面实现赋值.如果您提供以下之一,您的客户很容易获得相同的保证:
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:
- 无例外交换.
- 一个 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;
}
现在将有一些类型,使用交换实现复制分配是有意义的.然而,这些类型将是例外,而不是规则.
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.
开启:
void push_back(const value_type& val);
void push_back(value_type&& val);
想象一下 vector
其中:
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_type
放入 vector
将需要 2 个副本而不是 1 个,即使 capacity
> 已经足够了.这将是一场灾难,性能明智.
Then push_back
ing 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.
更新
这是一个您应该能够在任何符合 C++11 的平台上运行的 HelloWorld:
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
";
}
我用两种方式编写了 X
的复制赋值运算符:
I've coded X
's copy assignment operator two ways:
- 隐式,相当于调用
vector
的复制赋值运算符. - 使用复制/交换习语,暗示在宏
SLOW_DOWN
下.我想把它命名为SLEEP_FOR_AWHILE
,但如果你使用的是电池供电的设备,这种方式实际上比睡眠语句糟糕得多.
- Implicitly, which is equivalent to calling
vector
's copy assignment operator. - With the copy/swap idiom, suggestively under the macro
SLOW_DOWN
. I thought about naming itSLEEP_FOR_AWHILE
, but this way is actually much worse than sleep statements if you're on a battery powered device.
该测试在 0 到 1000 之间构造了一些随机大小的 vector
,并将它们分配了一百万次.它对每个时间进行计时,对时间求和,然后找到以浮点纳秒为单位的平均时间并将其打印出来.如果连续两次调用您的高分辨率时钟没有返回小于 100 纳秒的值,您可能需要增加向量的长度.
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.
这是我的结果:
$ 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%.天啊.
I'm seeing a 43% performance hit for the copy/swap idiom with this simple test. YMMV.
上述测试平均有一半的时间在 lhs 上有足够的容量.如果我们把它带到任何一个极端:
The above test, on average, has sufficient capacity on the lhs half the time. If we take this to either extreme:
- lhs 始终有足够的容量.
- lhs 从来没有足够的容量.
那么默认复制分配相对于复制/交换习语的性能优势从大约 560% 到 0% 不等.复制/交换习语永远不会更快,并且可能会显着变慢(对于此测试).
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).
想要速度?测量.
这篇关于什么时候重载传递引用(l 值和 r 值)优先于传递值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!