我已经看到它说用operator=
写一个相同类型by-value的参数作为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-value重载更可取
按值传递,或何时需要?我正在考虑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 :(得分:29)
对于复制赋值运算符可以回收资源的类型,使用副本进行交换几乎不是实现复制赋值运算符的最佳方法。例如,请查看std::vector
:
此类管理动态大小的缓冲区,并维护capacity
(缓冲区可容纳的最大长度)和size
(当前长度)。如果vector
复制赋值运算符已实现swap
,则无论如何,如果rhs.size() != 0
总是分配新缓冲区。
但是,如果lhs.capacity() >= rhs.size()
,则根本不需要分配新的缓冲区。人们可以简单地分配/构建从rhs
到lhs
的元素。当元素类型可以轻易地复制时,这可以归结为memcpy
。这可以比分配和释放缓冲区更快,更多。
std::string
的相同问题。
当MyType
的数据成员为MyType
和/或std::vector
时,std::string
会出现同样的问题。
您只想考虑使用swap实现复制分配的次数:
您知道swap
方法(包括rhs为左值时的强制复制构造)效率不会非常低。
您知道总是需要复制赋值运算符才能获得强大的异常安全保障。
如果您不确定2,换句话说,您认为复制赋值运算符可能有时需要强大的异常安全保证,请不要在交换方面实现赋值。如果您提供以下其中一项,您的客户很容易获得相同的保证:
例如:
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;
}
现在有一些类型可以使用swap实现复制赋值。但是这些类型将是例外,而不是规则。
在:
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_type
加入vector
将需要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&) = 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
的复制赋值运算符:
vector
的复制赋值运算符。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%。 YMMV。
平均而言,上述测试在lhs的一半时间内具有足够的容量。如果我们把这个带到极端:
然后,默认复制分配相对于复制/交换习惯用法的性能优势从大约560%到0%不等。复制/交换习语永远不会更快,并且可能会显着变慢(对于此测试)。
想要速度?测量