什么是复制elision,它是如何优化复制和交换的习惯用法?
我正在阅读复制和交换 。
我尝试阅读Copy Elision上的一些链接,但无法正确理解它的含义。 有人可以解释一下这个优化是什么,特别是下面的文字是什么意思
这不仅仅是一个方便的问题,实际上是一个优化。 如果参数绑定到一个左值(另一个非const对象),则在创build参数时会自动创build该对象的副本。 但是,当s绑定到右值(临时对象,文字)时,副本通常会被省略,从而保存对复制构造函数和析构函数的调用。 在参数接受为常量引用的赋值运算符的早期版本中,当引用绑定到右值时,复制elision不会发生。 这导致一个额外的对象被创build和销毁。
复制构造函数存在以进行复制。 在理论上,当你写一行如下:
CLASS c(foo());
编译器将不得不调用复制构造函数将foo()
的返回值复制到c
。
复制elision是一种跳过调用复制构造函数的方法,以免为开销付出代价。
例如,编译器可以安排foo()
将直接构造它的返回值到c
。
这是另一个例子。 假设你有一个function:
void doit(CLASS c);
如果使用实际参数调用它,编译器必须调用复制构造函数,以便不能修改原始参数:
CLASS c1; doit(c1);
但现在考虑一个不同的例子,假设你调用你的函数是这样的:
doit(c1 + c1);
operator+
将不得不创build一个临时对象(右值)。 不要在调用doit()
之前调用复制构造函数,编译器可以传递由operator+
创build的临时文件,并将其传递给doit()
。
这里是一个例子:
#include <vector> #include <climits> class BigCounter { public: BigCounter &operator =(BigCounter b) { swap(b); return *this; } BigCounter next() const; void swap(BigCounter &b) { vals_.swap(b); } private: typedef ::std::vector<unsigned int> valvec_t; valvec_t vals_; }; BigCounter BigCounter::next() const { BigCounter newcounter(*this); unsigned int carry = 1; for (valvec_t::iterator i = newcounter.vals_.begin(); carry > 0 && i != newcounter.vals_.end(); ++i) { if (*i <= (UINT_MAX - carry)) { *i += carry; } else { *i += carry; carry = 1; } } if (carry > 0) { newcounter.vals_.push_back(carry); } return newcounter; } void someFunction() { BigCounter loopcount; while (true) { loopcount = loopcount.next(); } }
在somefunction
, loopcount = loopcount.next();
从复制elision很大的好处。 如果不允许复制,则该行将需要3次调用复制构造函数和一个相关联的调用到析构函数。 在允许复制的情况下,可以将复制构造函数调用1次,在声明BigCount::next()
中显式调用。
如果operator =
已经被声明和定义如下:
BigCounter &BigCounter::operator =(const BigCounter &b) { BigCounter tmp(b); swap(tmp); return *this; }
那么拷贝构造函数就不得不被2次调用,即使是复制删除也是如此。 一个是构造newcounter
,另一个是构造tmp
。 而没有复制elision那里仍然是3.这就是为什么声明operator =
所以它的参数要求调用复制构造可以是使用赋值运算符的“复制和交换”习惯用法的优化。 当复制构造函数被调用来构造一个参数时,它的调用可能会被忽略,但是如果它被调用来创build一个局部variables,它可能不会被调用。