Потому что пример в той статье полностью корректен, и автор статьи абсолютно прав. Вот этот пример:
// Хорошо. struct person { person(std::string first_name, std::string last_name) : first_name{std::move(first_name)} // верно , last_name{std::move(last_name)} // std::move здесь СУЩЕСТВЕНЕН! {} private: std::string first_name; std::string last_name; };
Такой код позволяет покрыть все (ну ладно, почти все) возможные варианты использования класса:
std::string first{"abc"}, last{"def"}; person p1{first, last}; // (1) копирование обеих строк person p2{std::move(first), last}; // !!! копирование только второй person p2{std::move(first), std::move(last)}; // (3) нет копирования person p3{"x", "y"}; // нет копирования
Сравните со старым методом, когда передавали по const&: он однозначно хуже, потому что исключает вариант (3):
// Плоховато. struct person { person(const std::string& first_name, const std::string& last_name) : first_name{first_name} , last_name{last_name} {} private: std::string first_name; std::string last_name; }; std::string first{"abc"}, last{"def"}; person p1{first, last}; // будет копирование, как и хотели // Но что если мы точно знаем, что first и last нам больше не // понадобятся? мы не можем их в таком случае переместить // и добиться 0 копирований! Поэтому const& хуже.
Альтернативный вариант с && также хуже, но в обратную сторону:
// Странновато. struct person { person(std::string&& first_name, std::string&& last_name) : first_name{std::move(first_name)} , last_name{std::move(last_name)} {} private: std::string first_name; std::string last_name; }; std::string first{"abc"}, last{"def"}; person p1{std::move(first), std::move(last)}; // норм // но вот если мы НЕ хотим перемещения, то в случае && придется хитрить: person p2{std::string{first}, std::string{last}}; // FOOOO
Если не боитесь комбинаторного взрыва, то можете дать шанс && (но зачем? реального выигрыша в скорости не будет никакого, оптимизатор не дремлет):
// Пожалейте свою клавиатуру. struct person { person(std::string&& first_name, std::string&& last_name) : first_name{std::move(first_name)} , last_name{std::move(last_name)} {} person(const std::string& first_name, std::string&& last_name) : first_name{first_name} , last_name{std::move(last_name)} {} person(std::string&& first_name, const std::string& last_name) : first_name{std::move(first_name)} , last_name{last_name} {} person(const std::string& first_name, const std::string& last_name) : first_name{first_name} , last_name{last_name} {} private: std::string first_name; std::string last_name; };
Или вот то же самое, только с шаблонами (но опять же, зачем?):
// Заумно в данном случае (из пушки по воробьям), хотя в других может быть норм. struct person { template <typename T1, typename T2> person(T1&& first_name, T2&& last_name) : first_name{std::forward<T1>(first_name)} , last_name{std::forward<T2>(last_name)} {} private: std::string first_name; std::string last_name; };
Даже если у вас не std::string, а какой-то объект собственноручно написанного большущего класса, и вы хотите людей заставить перемещать его (а не копировать), то в таком случае лучше запретить конструктор копирование у этого большущего класса, нежели везде передавать его по &&. Так надежнее, да и код короче.
Напоследок пара вариантов, как делать НЕ СТОИТ:
// Кошмарно. struct person { person(const std::string& first_name, const std::string& last_name) : first_name{first_name} , last_name{last_name} {} private: // НЕТ и НЕТ: это мегаопасно, никогда не сохраняйте константные // ссылки в свойствах объекта const std::string& first_name; const std::string& last_name; }; person p{"x", "y"}; // ха-ха-ха, приехали
И так тоже не надо:
// Плоховато. struct person { person(std::string& first_name, std::string& last_name) : first_name{first_name} , last_name{last_name} {} private: // так можно иногда, но лучше все же воспользоваться shared_ptr: // будет медленнее, но безопасно std::string& first_name; std::string& last_name; };
Почему так происходит, каков фундаментальный принцип? Он прост: объект, как правило, должен ВЛАДЕТЬ своими свойствами.
Если объект не хочет чем-то владеть, то он может владеть shared_ptr’ом на это «что-то». Кстати, shared_ptr’ы в таком случае тоже надо передавать по значению, а не по константной ссылке — тут разницы с самым первым примером в начале статьи никакой:
// Лучше (если нет выхода). struct person { person(std::shared_ptr<portfolio> pf) : pf{std::move(pf)} // std::move здесь важен для производительности {} private: std::shared_ptr<portfolio> pf; }; auto psh = std::make_shared<portfolio>("xxx", "yyy", "zzz"); ... person p1{psh}; person p2{std::move(psh)}; // (X) так эффективнее, если psh больше не нужен
Обратите внимание: std::move для shared_ptr совершенно легален, он исключает накладные расходы на блокировку счетчика ссылок shared_ptr в памяти (сотни циклов CPU) и на его инкремент. Он никак не влияет на время жизни объекта и остальные ссылки на него. Но (X) можно делать, конечно, только в случае, если ссылка psh в коде ниже нам больше не нужна.
Мораль: не используйте const& повально. Смотрите по обстоятельствам.
P.S.
Используйте {} вместо () при передаче параметров конструктора. Модно, современно, молодежно.
ссылка на оригинал статьи https://habr.com/ru/post/460955/
Добавить комментарий