25

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

4 回答 4

36

穿上noexcept你的移动构造函数:

TestClass(TestClass&& other) noexcept {

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

在 C++03 中

vector<T>::push_back(T)

具有“强异常保证”。这意味着如果push_back抛出异常,向量将保持在调用push_back.

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

vector重新分配时,它想元素从旧缓冲区移动到新缓冲区但是,如果这些移动中的任何一个引发异常(除了第一个),那么它就会处于旧缓冲区已被修改的状态,并且新缓冲区还没有包含它应该包含的所有内容。vector无法将旧缓冲区恢复到其原始状态,因为它必须将元素移回这样做,这些移动也可能会失败。

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

  1. 如果T有一个noexcept移动构造函数,它可用于将元素从旧缓冲区移动到新缓冲区。

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

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

澄清:规则 2 中的“复制构造函数”是指采用 a 的构造函数const T&,而不是那些所谓的T&复制构造函数。:-)

于 2013-08-06T16:25:17.030 回答
15

noexcept在您的移动构造函数上使用:

TestClass(TestClass&& other) noexcept { ... }

noexcept没有像这样的常量表达式等价于noexcept(true).

编译器可以使用此信息来启用对非抛出函数的某些优化以及启用 noexcept 运算符,该运算符可以在编译时检查特定表达式是否被声明为抛出任何异常。

例如,如果元素的移动构造函数为 noexcept,则 std::vector 等容器将移动其元素,否则复制。

来源:http ://en.cppreference.com/w/cpp/language/noexcept_spec

注意:这是一个C++11特性。某些编译器可能还没有实现它......(例如:Visual Studio 2012

于 2013-08-06T16:28:45.137 回答
0

当内部的所有保留内存都被使用时,将调用复制构造函数std::vectorstd::vector::reserve()在添加元素之前需要调用方法。

vector<TestClass> vec;
vec.reserve(500000);
于 2017-08-02T14:45:13.733 回答
-1

另一个问题。在移动构造函数中,

// move constructor
TestClass(TestClass&& other){
    arr=other.arr;
    size=other.size;

    other.arr=nullptr;
    other.size=0;
}

难道不应该

arr=std:move(other.arr);

尺寸=标准:移动(其他尺寸);

因为

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

?

于 2016-02-13T05:43:01.133 回答