Как стать автором
Обновить

Комментарии 20

Чисто профессиональное замечание, перевод:
вы написали для класса конструктор копирования, который может делать все, и ожидаете
не равнозначен оригиналу:
you wrote a copy constructor for your class that could do anything
и более, того из-за этого теряется смысл всей статьи. Суть в некорректном переводе anything.
В англоязычном оригинале подразумевалось, что в конструкторе копирования можно делать всё что захочется, и что программист ожидает это поведение, то есть, что конструктор копирования будет вызван и какая-то часть кода, которая присутствует только там, будет вызвана соответственно, но из-за того, что компилятор удалил копию, это не произойдёт и часть кода не только не будет вызвана, а будет удалена.
В оригинале это как раз указано:
If compilers were to unpredictably remove copies, and thus remove pairs of copy/move constructor & destructor calls, they might break your code.
а вот перевод гораздо менее точный
Если компиляторы будут непредсказуемым образом удалять копии, тем самым также удаляя пары конструкторов & деструкторов копирования/перемещения, то это может привести к нарушению кода.
хотя формально он правильный, только более обтекаемый, обобщённый, что ли. Но именно это и ломает весь смысл фразы и статьи в итоге.
Спасибо за пояснение, поправим

И какие практические правила для эффективной передачи объектов?


  • Возврат "тяжелого" объекта из функции.
    • Например, я хочу вернуть вектор, строку итп. Как лучше всего поступать? Насколько мне известно, основная рекомендация: возвращать по значению и положиться на RVO/NVRO. Может ли тут быть ситуация, когда это не сработает? Как можно подсказать компилятору?
  • Перемещение (передача владения) в функцию
    • Привести к rvalue с помощью std::move?

А еще где-то неподалеку perfect forwarding...

В "return expr;" expr всегда является rvalue, то есть будет вызван конструктор перемещения, если он есть. Поэтому вектор можно спокойно возвращать по значению (без std::move)

Более того, если возвращать return std::move(expr); то rvo не будет, а будет конструктор перемещения всегда

Для собственного развития: а есть практические примеры в которых имеет смысл так делать?

Честно говоря не знаю. Может если компилятор не поддерживает rvo

Пишут, что лучше возвращать без move
так имеет смысл делать, если хочется переместить member структуры или класса

struct A { std::string s; };

std::string foo()
{
A a;
//...
return std::move(a).s; //если просто сделать a.s, то будет копирование
}
Уточню, что «всегда» относилось к контексту темы NRVO. В общем случае это не так, конечно.
Если не хочется задумываться о нюансах и потенциальных проблемах, можно возвращать через аргумент функции по ссылке/указателю, а не через return.
Но с учётом того, что возвращаемый объект модифицируется в процессе работы фукнции и, если функция раньше времени упадёт, то может остаться неоделанный объект, если исключение неправильн обработано будет.

Для начала стоит пользоваться C++20, у которого больше свободы в использовании неявного перемещения.
До него не каждый return перемещал.


https://oleksandrkvl.github.io/2021/04/02/cpp-20-overview.html#more-impl-moves

Насколько мне известно, основная рекомендация: возвращать по значению и положиться на RVO/NVRO. Может ли тут быть ситуация, когда это не сработает?
Скажем, в таком случае RVO сработает:
std::string foo = ...;
return foo;
а в таком нет:
std::string foo = ...;
std::string bar = ...;
return cond ? foo : bar;
поэтому во втором случае лучше добавлять std::move. Подробнее можно почитать у Майерса, он хорошо эти случаи разбирает.

А зачем вам ассемблерный вывод, заставили бы copy ctor и move ctor писать что-нибудь в cout, и всё, не надо дизассемблировать.


Upd: формально, не нашёл вашей ситуации в стандарте. Так что я так понимаю стандарт здесь не даёт никаких гарантий по copy elision.

какая-то не подробная статья. Рассмотрен лишь один пример, да и тот мягко говоря тривиален
И, конечно же, вы ожидаете, что, согласно правилам С++, этот конструктор будет вызван всякий раз когда объект вашего класса копируется. Но если компиляторы будут непредсказуемо удалять копирование, тем самым удаляя пары вызовов копирующего/перемещающего конструктора и деструктора они могут разрушить всю логику вашего кода.
RVO была допустима еще до с++11, и в каждом следующем стандарте расширяется набор кейсов, в которых компилятор вправе удалить лишние копирования/мувы. Так что если ваш код полагается на сайд эффекты копирований/мувов, то вы сам себе злобный буратино.
Автор оригинальной статьи забыл или игнорирует «правило пяти». Собственно пример и построен на том, что реализован конструктор копирования, а конструктор перемещения создан компилятором — классический пример применения этого правила.

Нельзя не заметить, что определённую оптимизацию, позволяющую избежать копирований временного объекта Widget, сделать всё-таки можно. Для этого мы вместо копирования продляем жизнь временному объекту (подробности), которая вернула функция doSomeVeryComplicatedThingWithSeveralArguments, а затем перемещаем его.


void someFunctionV3() {
    Widget&& complicatedThingResult =
        doSomeVeryComplicatedThingWithSeveralArguments(123, "hello");
    consume(std::move(complicatedThingResult));
}
нет особой разницы между этими вариантами:
Widget&& foo = someFunc();
Widget foo = someFunc();
Кроме крайне шаблонных вариантов, а-ля
std::forward
или универсальной ссылки вида auto&& foo = Foo(...);, где Foo() может возвращать результат либо по значению, либо по lvalue. По крайней мере в вашем примере код изменился только в том, что стал сложнее для прочтения начинающими плюсовиками.

Разницы нет в случае RVO/NRVO, но как вы же сами выше и продемонстрировали, есть ситуации, когда эти оптимизации не применяются. В случае же с rvalue ссылкой мы гарантируем отсутствие копирований временного объекта.

нет, не гарантируете. Код
Widget&& complicatedThingResult =
    doSomeVeryComplicatedThingWithSeveralArguments(...);
consume(complicatedThingResult); // без std::move
точно так же скомпилируется и в нем точно так же будет копирование, как и в варианте с не-ссылочной переменной.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий