Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - [124]
Даже когда вы имеете дело с функцией, выполняющей безусловное копирование копируемого типа с дешевым перемещением, бывают моменты, когда передача по значению может оказаться неприемлемой. Это связано с тем, что функция может копировать параметр двумя способами: с помощью конструирования (т.e. с помощью копирующего конструктора или перемещающего конструктора) и с помощью присваивания (т.e. с помощью оператора копирующего или перемещающего присваивания). Функция >addName
использует конструирование: ее параметр >newName
передается функции >vector::push_back
, и внутри этой функции >newName
конструируется копированием в новом элементе в конце вектора >std::vector
. Для функций, которые используют конструирование для копирования своего параметра, анализ, который мы видели ранее, завершен: использование передачи по значению приводит к дополнительному перемещению как для lvalue-аргументов, так и для rvalue-аргументов.
Когда параметр копируется с использованием присваивания, ситуация становится более сложной. Предположим, например, что у нас есть класс, представляющий пароли. Поскольку пароль может изменяться, мы предоставляем функцию установки >changeTo
. Используя стратегию передачи по значению, реализовать >Password
можно следующим образом:
>class Password {
>public:
> explicit Password(std::string pwd) // Передача по значению;
> : text(std::move(pwd)) {} // конструирование text
> void changeTo(std::string newPwd) // Передача по значению;
> { text = std::move(newPwd); } // присваивание text
> …
>private:
> std::string text; // Текст пароля
>};
Хранение паролей в виде обычного текста приведет специалистов по безопасности в неистовство, но мы их проигнорируем и рассмотрим следующий код:
>std::string initPwd("Supercalifragilisticexpialidocious");
>Password p(initPwd);
Здесь нет никаких сюрпризов: >p.text
конструируется с использованием заданного пароля, а применение передачи по значению в конструкторе приводит к стоимости перемещающего конструирования >std::string
, которое может оказаться излишним при применении перегрузки или прямой передачи. Все в порядке.
Пользователь этой программы может быть не столь оптимистичным насчет пароля, так как слово “Supercalifragilisticexpialidocious” можно найти в словаре. А потому он предпринимает действия, которые ведут к выполнению следующего кода:
>std::string newPassword = "Beware the Jabberwock";
>p.changeTo(newPassword);
Лучше новый пароль старого или нет — вопрос сложный, но это проблемы пользователя. Нашей же проблемой является то, что необходимость функции >changeTo
использовать присваивание (а не конструирование) для копирования параметра >newPwd
, вероятно, приведет к росту стоимости стратегии передачи параметра по значению.
Аргумент, переданный функции >changeTo
, представляет собой lvalue (>newPassword
), так что, когда конструируется параметр >newPwd
, вызывается копирующий конструктор >std::string
. Этот конструктор выделяет память для хранения нового пароля. Затем >newPwd
присваивается с перемещением переменной >text
, что приводит к освобождению памяти, которая ранее принадлежала этой переменной >text
. Таким образом, в >changeTo
выполняются два действия по управлению динамической памятью: одно выделяет память для хранения нового пароля, а второе освобождает память, в которой хранился старый пароль.
Но в данном случае старый пароль (“Supercalifragilisticexpialidocious”) длиннее нового (“Beware the Jabberwock”), так что нет необходимости в выделении и освобождении памяти вовсе. Если бы использовался подход с перегрузкой, вероятно, никакие выделения и освобождения не выполнялись бы:
>class Password {
>public:
> …
> void changeTo(const std::string& newPwd) // Перегрузка для
> { // lvalue
> text = newPwd; // При text.capacity() >= newPwd.size()
> // можно использовать память text
> }
> …
>private:
> std::string text; // Как выше
>};
В этом сценарии стоимость передачи по значению включает дополнительное выделение и освобождение памяти — стоимость, которая, скорее всего, превысит стоимость операции по перемещению >std::string
на порядки.
Интересно, что если старый пароль короче нового, то обычно невозможно избежать при присваивании действий по выделению и освобождению памяти, и в этом случае передача по значению будет выполняться практически с той же скоростью, что и передача по ссылке. Таким образом, стоимость копирования параметров с помощью присваивания может зависеть от объектов, участвующих в присваивании! Этот вид анализа применим к любому типу параметров, который хранит данные в динамически выделенной памяти. Не все типы таковы, но многие — включая >std::string
и >std::vector
— обладают этим свойством.
Это потенциальное увеличение стоимости в общем случае применимо только к передаче аргументов, являющихся lvalue, поскольку необходимость выделения и освобождения памяти обычно возникает только тогда, когда выполняется истинное копирование (не перемещение). В случае rvalue-аргументов перемещений почти всегда достаточно.
Получается, что дополнительная стоимость передачи по значению (по сравнению с передачей по ссылке) для функций, копирующих параметр с использованием присваивания, зависит от передаваемого типа, соотношения lvalue- и rvalue-аргументов и от того, использует ли тип динамическую память (и, если использует, то от реализации операторов присваивания для данного типа и вероятности того, что память, связанная с целевым объектом присваивания, как минимум того же размера, что и память присваиваемого объекта). В случае
![Эффективное использование STL](/storage/book-covers/52/523cd96c04eb6eb343b29e2dcdc9dd098b664c00.jpg)
В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.
![Как функции, не являющиеся методами, улучшают инкапсуляцию](/build/oblozhka.dc6e36b8.jpg)
Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.
![Изучаем Java EE 7](/storage/book-covers/e0/e0ee9b7e3e4f168a93df98d7e47d66089eac3652.jpg)
Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)
![Геймдизайн. Рецепты успеха лучших компьютерных игр от Super Mario и Doom до Assassin’s Creed и дальше](/storage/book-covers/d0/d0fc13172d4310c9da7b10ba57a3fcb2e3d9f10d.jpg)
Что такое ГЕЙМДИЗАЙН? Это не код, графика или звук. Это не создание персонажей или раскрашивание игрового поля. Геймдизайн – это симулятор мечты, набор правил, благодаря которым игра оживает. Как создать игру, которую полюбят, от которой не смогут оторваться? Знаменитый геймдизайнер Тайнан Сильвестр на примере кейсов из самых популярных игр рассказывает как объединить эмоции и впечатления, игровую механику и мотивацию игроков. Познакомитесь с принципами дизайна, которыми пользуются ведущие студии мира! Создайте игровую механику, вызывающую эмоции и обеспечивающую разнообразие.
![Перевод в электронный формат, кодированные наборы шрифтов и система Оптического Распознавания Символов для многошрифтовых информационных ресурсов на примере “Летописи журнальных статей”](/build/oblozhka.dc6e36b8.jpg)
В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.
![Java 7](/storage/book-covers/0c/0c854895249b5022dc4112a36f32ee3e02d18559.jpg)
Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.
![Экстремальное программирование. Разработка через тестирование](/storage/book-covers/dd/dda689ec4f767ab4657b7012084c0637cde96fb0.jpg)
Возвращение знаменитого бестселлера. Изящный, гибкий и понятный код, который легко модифицировать, который корректно работает и который не подкидывает своим создателям неприятных сюрпризов. Неужели подобное возможно? Чтобы достичь цели, попробуйте тестировать программу еще до того, как она написана. Именно такая парадоксальная идея положена в основу методики TDD (Test-Driven-Development – разработка, основанная на тестировании). Бессмыслица? Не спешите делать скороспелые выводы. Рассматривая применение TDD на примере разработки реального программного кода, автор демонстрирует простоту и мощь этой методики.
![Обработка событий в С++](/build/oblozhka.dc6e36b8.jpg)
В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.