С++ 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 раз.
Любая подсказка?
Ответы
Ответ 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)
?