Подтвердить что ты не робот

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

Я тестировал некоторые функции С++ 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 раз.

Любая подсказка?

4b9b3361

Ответ 1

Поместите noexcept в свой конструктор перемещения:

TestClass(TestClass&& other) noexcept {

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

В С++ 03

vector<T>::push_back(T)

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

Эта гарантия проблематична, если конструктор перемещения генерирует исключение.

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

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

NB: Это С++ 11. Определенный компилятор, возможно, еще не реализовал его... (например: Visual Studio 2012)

Ответ 3

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

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

Ответ 4

Другой вопрос. В конструкторе перемещения

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

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

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

обр = станд: перемещение (other.arr);

размер = станд: перемещение (other.size);

потому что

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

?