C ++ 11右值引用也调用了拷贝构造函数

时间:2013-08-06 16:17:03

标签: c++ c++11 rvalue-reference move-constructor

我一直在测试一些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次。

有任何线索吗?

4 个答案:

答案 0 :(得分:32)

noexcept放在移动构造函数上:

TestClass(TestClass&& other) noexcept {

详细说明:我打算给这一个皮埃尔,但不幸的是,cppreference源只是大致正确。

在C ++ 03中

vector<T>::push_back(T)

具有“强烈的例外保证”。这意味着如果push_back抛出异常,则向量将保持与调用push_back之前相同的状态。

如果移动构造函数抛出异常,则此保证会有问题。

vector重新分配时,喜欢移动元素从旧缓冲区移动到新缓冲区。但是,如果这些移动中的任何一个抛出异常(除了第一个),那么它将处于旧缓冲区已被修改的状态,并且新缓冲区尚未包含它应该包含的所有内容。 vector无法将旧缓冲区恢复到其原始状态,因为它必须移回元素才能执行此操作,这些移动也可能会失败。

因此为C ++ 11制定了规则:

  1. 如果Tnoexcept移动构造函数,则可以使用该构造函数将元素从旧缓冲区移动到新缓冲区。

  2. 否则如果T有复制构造函数,则会使用它。

  3. 否则(如果没有可访问的复制构造函数),则毕竟将使用移动构造函数,但在这种情况下,不再给出强异常安全保证。

  4. 澄清:规则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);

因为

  

事实上所有命名值(例如函数参数)总是计算为左值(即使是那些声明为右值引用的值)