Эффективный и современный С++. 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

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Изучаем Java EE 7

Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)


Геймдизайн. Рецепты успеха лучших компьютерных игр от Super Mario и Doom до Assassin’s Creed и дальше

Что такое ГЕЙМДИЗАЙН? Это не код, графика или звук. Это не создание персонажей или раскрашивание игрового поля. Геймдизайн – это симулятор мечты, набор правил, благодаря которым игра оживает. Как создать игру, которую полюбят, от которой не смогут оторваться? Знаменитый геймдизайнер Тайнан Сильвестр на примере кейсов из самых популярных игр рассказывает как объединить эмоции и впечатления, игровую механику и мотивацию игроков. Познакомитесь с принципами дизайна, которыми пользуются ведущие студии мира! Создайте игровую механику, вызывающую эмоции и обеспечивающую разнообразие.


Программирование приложений для мобильных устройств под управлением Android. Часть 1

Книга посвящена разработке программ для мобильных устройств под управлением операционной системы Android. Рассматривается создание приложений с использованием системных компонентов и служб Android. Приведены базовые данные о структуре приложений, об основных классах и их методах, сопровождаемые примерами кода. Часть 1 содержит шесть глав, описывающих основные принципы создания приложений, пользовательский интерфейс, полномочия приложений, а так же базовые классы: Activity, Intent, Fragment. Книга предназначена для программистов, владеющих языком программирования Java и желающих освоить написание приложений, работающих под ОС Android.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Создаем порт для FreeBSD своими руками. Часть II

Система сборки программ, используемая во FreeBSD, имеет значительно большие возможности, чем те, которые мы задействовали. Какие это возможности и как их использовать в своих портах?


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.