我一直在测试一些C ++ 11的一些功能。 我遇到了r值引用并移动了构造函数。
我实现了我的第一个移动构造函数,这里是:
#include <iostream>
#include <vector>
using namespace std;
class TestClass{
public:
TestClass(int s):
size(s), arr(new int[s]){
}
~TestClass(){
if (arr)
delete arr;
}
// copy constructor
TestClass(const TestClass& other):
size(other.size), arr(new int[other.size]){
std::copy(other.arr, other.arr + other.size, arr);
}
// move constructor
TestClass(TestClass&& other){
arr=other.arr;
size=other.size;
other.arr=nullptr;
other.size=0;
}
private:
int size;
int * arr;
};
int main(){
vector<TestClass> vec;
clock_t start=clock();
for(int i=0;i<500000;i++){
vec.push_back(TestClass(1000));
}
clock_t stop=clock();
cout<<stop-start<<endl;
return 0;
}
代码工作正常。无论如何把一个std :: cout放在复制构造函数中我注意到它被调用了!并且很多次..(移动构造函数500000次,复制构造函数524287次)。
让我感到惊讶的是,如果我从代码中注释掉复制构造函数,整个程序会更快,而这次移动构造函数被称为1024287次。
有任何线索吗?
答案 0 :(得分:32)
将noexcept
放在移动构造函数上:
TestClass(TestClass&& other) noexcept {
详细说明:我打算给这一个皮埃尔,但不幸的是,cppreference源只是大致正确。
在C ++ 03中
vector<T>::push_back(T)
具有“强烈的例外保证”。这意味着如果push_back
抛出异常,则向量将保持与调用push_back
之前相同的状态。
如果移动构造函数抛出异常,则此保证会有问题。
当vector
重新分配时,喜欢将移动元素从旧缓冲区移动到新缓冲区。但是,如果这些移动中的任何一个抛出异常(除了第一个),那么它将处于旧缓冲区已被修改的状态,并且新缓冲区尚未包含它应该包含的所有内容。 vector
无法将旧缓冲区恢复到其原始状态,因为它必须移回元素才能执行此操作,这些移动也可能会失败。
因此为C ++ 11制定了规则:
如果T
有noexcept
移动构造函数,则可以使用该构造函数将元素从旧缓冲区移动到新缓冲区。
否则如果T
有复制构造函数,则会使用它。
否则(如果没有可访问的复制构造函数),则毕竟将使用移动构造函数,但在这种情况下,不再给出强异常安全保证。
澄清:规则2中的“复制构造函数”表示构造函数采用const T&
,而不是其中一个所谓的T&
复制构造函数。 : - )
答案 1 :(得分:14)
在移动构造函数上使用noexcept
:
TestClass(TestClass&& other) noexcept { ... }
没有像这样的常量表达式的 noexcept
等同于noexcept(true)
。
编译器可以使用此信息对非抛出函数启用某些优化,并启用noexcept运算符,该运算符可以在编译时检查是否声明了特定表达式抛出任何异常。
例如,std :: vector等容器将移动其元素,如果元素&#39;移动构造函数是noexcept,否则复制。
来源:http://en.cppreference.com/w/cpp/language/noexcept_spec
注意:这是 C ++ 11 功能。某些编译器可能尚未实现它......(例如:Visual Studio 2012)
答案 2 :(得分:0)
当使用std::vector
内的所有保留内存时,将调用复制构造函数。在添加元素之前,有必要调用std::vector::reserve()
方法。
vector<TestClass> vec;
vec.reserve(500000);
答案 3 :(得分:-1)
另一个问题。在移动构造函数中,
// move constructor
TestClass(TestClass&& other){
arr=other.arr;
size=other.size;
other.arr=nullptr;
other.size=0;
}
不应该
ARR = STD:移动(other.arr);
尺寸= STD:移动(other.size);
因为
事实上所有命名值(例如函数参数)总是计算为左值(即使是那些声明为右值引用的值)