std::vector<some_type> some_func(...) {
std::vector<some_type> result;
......
//Some code to fill vector
......
return result;
}
....
std::vector<some_type> need = some_func(...)
Что происходило в С++98 (сейчас говорим про стандарт, а не про оптимизацию компиляторов):
1. создается (конструируется) вектор need
2. выполняется функция some_func, в которой создается (конструируется) вектор result
3. по окончанию выполнения функции происходит копирование данных из вектора result в вектор need
5. выполняется деструктор вектора result
6. по выходу из области видимости выполняется деструктор вектора need
Что происходит в C++11 (неправильное зачеркнуть, описываю в грубом своем представлении, опять же разговор про стандарт):
1. если компилятор видит, что у std::vector<some_type> есть перемещающий конструктор, то вектор need фактически не создается (конструктор не выполняется), а создается "ссылка"
2. выполняется функция some_func, в которой создается (конструируется) вектор result
3. по окончанию выполнения функции происходит копирование "ссылки" вектора result в "ссылку" вектор need (десруктор result не выполняется)
4. по выходу из области видимости выполняется деструктор вектора need (ранее result)
Таким образом мы избавляемся от одного конструктора и одного деструктора? Или же конструкторы и деструкторы будут вызваны оба раза, а при перемещении произойдет лишь "передача" внутренностей?
нет, неправильно, вектор будет создан и там и там (если не рассматривать работу оптимизатора). В первом приближении код с перемещением эквивалентен следующему:
void some_func(..., OUT std::vector<some_type>& ret) {
std::vector<some_type> result;
......
//Some code to fill vector
......
ret.swap(result);
}
>Или же конструкторы и деструкторы будут вызваны оба раза, а при перемещении произойдет лишь "передача" внутренностей?
вот это
Здравствуйте, dosik, Вы писали:
D>Таким образом мы избавляемся от одного конструктора и одного деструктора? Или же конструкторы и деструкторы будут вызваны оба раза, а при перемещении произойдет лишь "передача" внутренностей?
оптимизации не учитываем
C++98
1. выполняется some_func
2. создается result
3. при выходе из функции вектор копируется во временную переменную. result уничтожается (вызывается деструктор)
4. создается need, при этом вызывается конструктор копии из временной переменной. временный объект уничтожается
5. по выходу из области видимости выполняется деструктор вектора need
С++11
все то же самое, только пару изменений:
1) в пункте 3 вместо "вектор копируется" надо написать "вектор перемещается"
2) в пункте 4 вместо "конструктор копии" надо написать "конструктор перемещения"
Таким образом кол-во деструкций не изменяется, а вместо копирований срабатывают перемещения
Здравствуйте, uzhas, Вы писали:
U>С++11
U>все то же самое, только пару изменений: U>1) в пункте 3 вместо "вектор копируется" надо написать "вектор перемещается" U>2) в пункте 4 вместо "конструктор копии" надо написать "конструктор перемещения"
U>Таким образом кол-во деструкций не изменяется, а вместо копирований срабатывают перемещения
А разве с приведенном коде не будет тоже самое что и в C++98, т.е. копирование?
Здравствуйте, Videoman, Вы писали:
V>А разве с приведенном коде не будет тоже самое что и в C++98, т.е. копирование?
12.8/32 же
When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
Здравствуйте, antropolog, Вы писали:
A>Здравствуйте, Videoman, Вы писали:
V>>А разве с приведенном коде не будет тоже самое что и в C++98, т.е. копирование?
A>12.8/32 же
A>
A>When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
Понял, спасибо. Как же все все-таки запутано в С++11, с move семантикой.
Здравствуйте, Constructor, Вы писали:
C>Мне одному кажется, что оба стандарта (и С++03, и C++11) говорят, что в данном случае должна использоваться copy elision?
Выше я уже задавал вопрос. Вы не согласны с ответом? Почему спрашиваю — я в С++11 пока не очень ориентируюсь.
Здравствуйте, Constructor, Вы писали:
C>Мне одному кажется, что оба стандарта (и С++03, и C++11) говорят, что в данном случае должна использоваться copy elision?
Где-то тут читал, что:
std::vector<some_type> need = some_func(...)
Аналогично:
std::vector<some_type> need = std::move(some_func(...))
Т.е. при наличии у возвращаемого из функции типа не только копирующего, но перемещающего конструктора, предпочтение отдается последнему. Программист же выбирает наиболее удобочитаемый стиль написания кода.
Здравствуйте, dosik, Вы писали:
D>Т.е. по прежнему дешевле передавать в функцию ссылку на уже созданный объект — тогда вместо трех выполняем только один конструктор/деструктор.
Здравствуйте, dosik, Вы писали:
D>Т.е. по прежнему дешевле передавать в функцию ссылку на уже созданный объект — тогда вместо трех выполняем только один конструктор/деструктор.
вот вы уже лихо перешли на "дешевле", хотя изначально спросили про стандартное поведение
когда вы хотите "дешевле", то стандарт тут не помощник, тут надо уже анализировать компиляторы с их оптимизациями (copy elision, (N)RVO, inlining)
тут вся соль в том, что кол-во копирований в C++11 уменьшили до нуля. кол-во вызовов конструкторов\деструкторов на практике может не играть никакой роли, т.к. действий там мало и они вообще могут быть удалены оптимизатором
передавать ссылку на уже созданный вектор может быть дешевле в случае, если вектор внутри уже саллоцировал память (сделали reserve) и функция запихивает туда столько данных, что переаллокаций не происходит
короче, возвращать по значению сейчас не зазорно. профилируйте, а потом оптимизируйте.
Здравствуйте, uzhas, Вы писали:
U>передавать ссылку на уже созданный вектор может быть дешевле в случае, если вектор внутри уже саллоцировал память (сделали reserve) и функция запихивает туда столько данных, что переаллокаций не происходит
Ну тут не поспоришь...
U>короче, возвращать по значению сейчас не зазорно.
Старики могут засмеять
D>Таким образом мы избавляемся от одного конструктора и одного деструктора? Или же конструкторы и деструкторы будут вызваны оба раза, а при перемещении произойдет лишь "передача" внутренностей?
По стандарту 98 поведение должно быть таким:
std::vector<some_type>& some_func(void* resultBuffer, ...) {
std::vector<some_type> result;
......
//Some code to fill vector
......
return *::new(resultBffer) std::vector<some_type>( result );
}
....
// выделили место под std::vector<some_type> need;
std::vector<some_type>&need = some_func(место_под_need, ...);
// с этого момента вызывающая сторона отвечает за разрушение need
и с 98 было в стандарте же можно делать ВНУТРИ ФУНКЦИИ RVO, а с 03 и NRVO
В данном случае NRVO будет означать следующее:
std::vector<some_type>& some_func(void* resultBuffer, ...) {
std::vector<some_type>& result = *new(resultBffer) std::vector<some_type>;
// с этого момента и до return some_func отвечает за разрушение result
......
//Some code to fill vector
......
return result; // с этого момента за разрушение result отвечает вызывающая сторона
}
RVO же, означает следующее:
std::vector<some_type>& some_func(void* resultBuffer, ...) {
//тут код, который вычисляет параметры конструктора std::vector<some_type>
......
return *::new(resultBffer) std::vector<some_type>( те самые параметры конструктора );
}
Для этого исходную some_func надо писать так:
std::vector<some_type> some_func(...) {
//тут код, который вычисляет параметры конструктора std::vector<some_type>
......
return std::vector<some_type>( те самые параметры конструктора );
}
И для такой редакции в С++11 будет доступна move-семантика. То есть эквивалентный код будет такой:
std::vector<some_type>& some_func(void* resultBuffer, ...) {
//тут код, который вычисляет параметры конструктора std::vector<some_type>
......
std::vector<some_type> __temporary_object__( те самые параметры конструктора );
return *new(resultBffer) std::vector<some_type>( static_cast<std::vector<some_type>&&>( __temporary_object__ ) );
}
Правда, насколько я понимаю, RVO всё равно доступна компилятору, так как у std::vector есть доступный конструктор копии.
Но я не понимаю зачем тут move-семантика, если у возвращаемого типа есть доступный конструктор копии.
Другое дело, если мы, например, std::unique_ptr захотим вернуть...
При этом в любом случае ВЫЗЫВАЮЩИЙ код от того, что ВНУТРИ функции пишут зависеть не должен, если не включена оптимизация вызывающего кода, использующая доступ к определению тела функции в точке вызова (то, что обычно понимают под inline)
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, uzhas, Вы писали:
U>достаточно писать U>
U>return result;
Да, точно, это я тупанул.
Спасибо.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, uzhas, Вы писали:
U>здесь уничтожено три объекта типа A U>всю ответственность за потери беру на себя U>это поведение вполне соответствует стандарту
вполне соответствует стандарту (12.8/31) и отсутствие промежуточной переменной, и данное (дефолтное) поведение вы беспардонно отключили ключом компиляции (-fno-elide-constructors). ТС тянется к знаниям, а вы его только сбиваете. Нехорошо.