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


Платформа J2Me

Эта книга научит вас, как разрабатывать программное обеспечение для платформы J2ME компании «Sun Microsystems». Эта книга придерживается стиля учебного пособия, это не справочное руководство.Цель — дать вам твердую основу в понятиях и техниках, которая даст вам возможность решиться на самостоятельную разработку качественных приложений.


Виртуальная библиотека Delphi

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


Обработка баз данных на Visual Basic.NET

Это практическое руководство разработчика программного обеспечения на Visual Basic .NET и ADO.NET, предназначенное для создания приложений баз данных на основе WinForms, Web-форм и Web-служб. В книге описываются практические способы решения задач доступа к данным, с которыми сталкиваются разработчики на Visual Basic .NET в своей повседневной деятельности. Книга начинается с основных сведений о создании баз данных, использовании языка структурированных запросов SQL и системы управления базами данных Microsoft SQL Server 2000.


Исчерпывающее руководство по написанию всплывающих подсказок

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


Программное обеспечение встроенных систем. Общие требования к разработке и документированию

Embedded system software. General requirements for development and documentationСтандарт подготовлен в развитие ГОСТ Р ИСО/МЭК 12207-99 «Информационная технология. Процессы жизненного цикла программных средств» с целью учета специфики разработки и документирования программного обеспечения встроенных систем реального времени.