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

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

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

24

Решение

Положил noexcept на ходу конструктор:

TestClass(TestClass&& other) noexcept {

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

В С ++ 03

vector<T>::push_back(T)

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

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

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

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

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

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

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

Пояснение: «конструктор копирования» в правиле 2 означает, что конструктор принимает const T&не один из тех, кто так называемый weenie T& Копировать конструкторы. 🙂

32

Другие решения

использование 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)

14

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

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

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

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

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

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

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

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

так как

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

?

-1
По вопросам рекламы [email protected]