C++11 rvalue ссылка вызов конструктора копирования тоже

Я тестировал некоторые функции C++11 из некоторых. Я наткнулся на ссылки r-value и конструкторы перемещения.

я реализовал свой первый конструктор перемещения, вот он:

#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 ответов


поставить noexcept на вашем конструкторе перемещения:

TestClass(TestClass&& other) noexcept {

уточнение: я собирался дать это Пьеру, но, к сожалению, источник cppreference только приблизительно правильный.

В C++03

vector<T>::push_back(T)

имеет "сильную гарантию исключения". Это означает, что если push_back выбрасывает исключение, вектор остается в том же состоянии, что и до вызова push_back.

эта гарантия проблематична если движение конструктор создает исключение.

когда vector перераспределяет, это было как to движение элементы из старого буфера в новый. Однако, если какой-либо из этих ходов вызывает исключение (кроме первого), то он остается в состоянии, когда старый буфер был изменен, а новый буфер еще не содержит всего, что он должен. The vector не удается восстановить старый буфер в исходное состояние, потому что он должен был бы двигаться элементы для этого те движется, возможно, тоже не удастся.

Итак, для C++11 было установлено правило:

  1. если T есть noexcept переместить конструктор, который можно использовать для перемещения элементов из старого буфера в новый.

  2. в противном случае, если T имеет конструктор копирования, что будет использоваться вместо этого.

  3. в противном случае (если нет доступного конструктора копирования), то перемещение конструктор будет использоваться в конце концов, однако в этом случае сильная гарантия безопасности исключений больше не предоставляется.

уточнение: "конструктор копирования" в правиле 2 означает, что конструктор принимает const T&, не один из тех сосисок так называемых T& конструкторы копирования. :-)


использовать noexcept на вашем конструкторе перемещения:

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

noexcept без такого постоянного выражения эквивалентно noexcept(true).

компилятор может использовать эту информацию для включения определенных оптимизаций на функции, не бросающие, а также включить оператор noexcept, который может проверить во время компиляции, если определенное выражение объявлено, чтобы бросить какие-либо исключения.

например, контейнеры, такие как std::vector будут перемещаться их элементы, если конструктор перемещения элементов является noexcept, и копировать в противном случае.

источник:http://en.cppreference.com/w/cpp/language/noexcept_spec

NB : это C++11 характеристика. Возможно, некоторые компиляторы еще не реализовали его... (пример: Visual Studio 2012)


конструктор копирования вызывается, когда вся зарезервированная память внутри есть. Надо позвонить std::vector::reserve() метод перед добавлением элементов.

vector<TestClass> vec;
vec.reserve(500000);

еще один вопрос. В конструкторе move,

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

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

не должно быть

arr=std: move (other.arr);

size=std: move (другое.размер);

, потому что

тот факт, что все именованные значения (такие как параметры функции) всегда оцениваются как lvalues (даже те, которые объявлены как ссылки rvalue)

?