#c #copy-constructor #move-constructor
Вопрос:
Я тестировал некоторые функции 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 TestClassamp; other):
size(other.size), arr(new int[other.size]){
std::copy(other.arr, other.arr other.size, arr);
}
// move constructor
TestClass(TestClassamp;amp; 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. Какой компилятор вы используете?
2. coliru.stacked-crooked.com/…
3. @pinoscotto Просто примечание: у вас неопределенное поведение , вы должны использовать
delete[]
, и вам не нуженif (arr) ...
деструктор. Поэтому деструктор должен быть простым~TestClass(){ delete[] arr; }
.4. @pinoscotto Всегда безопасно звонить
delete[]
без if: (1) еслиnew
при инициализации происходит инициализация, деструктор не будет вызван (2) в противном случае вы либо укажете действительный адрес (3), либо у вас есть anullptr
, и всегда безопасно удалить anullptr
(гарантируется стандартом).
Ответ №1:
Включи noexcept
свой конструктор ходов:
TestClass(TestClassamp;amp; other) noexcept {
Уточнение: Я собирался рассказать об этом Пьеру, но, к сожалению, источник cppreference только приблизительно верен.
В C 03
vector<T>::push_back(T)
имеет «гарантию строгого исключения». Это означает, что если push_back
возникает исключение, вектор остается в том же состоянии, в котором он был до вызова push_back
.
Эта гарантия проблематична, если конструктор перемещения создает исключение.
Когда vector
происходит перераспределение, он хотел бы переместить элементы из старого буфера в новый. Однако, если какое-либо из этих перемещений вызывает исключение (кроме первого), оно остается в состоянии, в котором старый буфер был изменен, а новый буфер еще не содержит всего, что он должен содержать. vector
Не удается восстановить старый буфер в исходное состояние, потому что для этого ему придется переместить элементы обратно, эти перемещения также могут завершиться неудачей.
Таким образом, для C 11 было установлено правило:
- Если
T
естьnoexcept
конструктор перемещения, его можно использовать для перемещения элементов из старого буфера в новый. - В противном случае, если
T
у вас есть конструктор копирования, он будет использоваться вместо этого. - В противном случае (если нет доступного конструктора копирования), то конструктор перемещения все-таки будет использоваться, однако в этом случае гарантия безопасности строгих исключений больше не предоставляется.
Уточнение: «конструктор копирования» в правиле 2 означает конструктор , принимающий a const Tamp;
, а не один из так называемых конструкторов Tamp;
копирования. 🙂
Комментарии:
1. можете ли вы подробнее рассказать о своем ответе и о том, что делает noexcept?
2. Все в Visual studio 🙁 кроме слов «ошибка C3646: «за исключением»: неизвестный спецификатор переопределения»
3. @Алон accu.org/index.php/conferences/accu_conference_2013/…
4. @Alon здесь обсуждается соответствующая, хотя и предшествующая ратификации C 11, дискуссия . В основном речь идет о сохранении определенных гарантий исключений в стандартных библиотечных контейнерах.
Ответ №2:
noexcept
конструктор для перемещения :
TestClass(TestClassamp;amp; other) noexcept { ... }
noexcept
без такого постоянного выражения это эквивалентно noexcept(true)
.
Источник : http://en.cppreference.com/w/cpp/language/noexcept_specКомпилятор может использовать эту информацию для включения определенных оптимизаций для функций, не вызывающих исключений, а также для включения оператора noexcept, который может проверять во время компиляции, объявлено ли конкретное выражение для создания каких-либо исключений.
Например, контейнеры, такие как std::vector, будут перемещать свои элементы, если конструктор перемещения элементов отсутствует, и копировать в противном случае.
ПРИМЕЧАНИЕ : Это функция C 11. Определенный компилятор, возможно, еще не реализовал его… (например, Visual Studio 2012)
Ответ №3:
Конструктор копирования вызывается, когда используется вся зарезервированная память внутри std::vector
. std::vector::reserve()
Перед добавлением элементов необходимо вызвать метод.
vector<TestClass> vec;
vec.reserve(500000);
Ответ №4:
Еще один вопрос. В конструкторе перемещения,
// move constructor
TestClass(TestClassamp;amp; other){
arr=other.arr;
size=other.size;
other.arr=nullptr;
other.size=0;
}
Разве это не должно быть
arr=std:перемещение(другое.arr);
размер=std:перемещение(другой размер);
потому что
тот факт, что все именованные значения (например, параметры функции) всегда оцениваются как значения lvalue (даже те, которые объявлены как ссылки на значения rvalue)
?