Скотт Мейерс - Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14
- Название:Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:2016
- Город:Москва
- ISBN:978-5-8459-2000-3
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Мейерс - Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 краткое содержание
В книге рассматриваются следующие темы. Освоение С++11 и С++14 — это больше, чем просто ознакомление с вводимыми этими стандартами возможностями (например, объявлениями типов
, семантикой перемещения, лямбда-выражениями или поддержкой многопоточности). Вопрос в том, как использовать их эффективно, чтобы создаваемые программы были корректны, эффективны и переносимы, а также чтобы их легко можно было сопровождать. Именно этим вопросам и посвящена данная книга, описывающая создание по-настоящему хорошего программного обеспечения с использованием C++11 и С++14 — т.е. с использованием современного С++.
■ Преимущества и недостатки инициализации с помощью фигурных скобок, спецификации
, прямой передачи и функций
интеллектуальных указателей
■ Связь между
,
, rvalue-ссылками и универсальными ссылками
■ Методы написания понятных, корректных,
лямбда-выражений
■ Чем
отличается от
, как они используются и как соотносятся с API параллельных вычислений С++
■ Какие из лучших методов “старого” программирования на С++ (т.е. С++98) должны быть пересмотрены при работе с современным С++
Более чем 20 лет книги
серии
являются критерием уровня книг по программированию на С++. Понятное пояснение сложного технического материала принесло ему всемирную известность. Он всегда самый желанный гость на международных конференциях, а его услуги консультанта широко востребованы во всем мире.
Скотт Мейерс Эффективный и современный С++, После изучения основ С++ я перешел к изучению того, как применять С++ в промышленном программировании, с помощью серии книг Скотта Мейерса Эффективный С++. Эффективный и современный С++ — наиболее важная из книг серии, предлагающая ключевые рекомендации, стили и идиомы, позволяющие эффективно использовать современный С++. Вы еще не купили эту книгу? Сделайте это прямо сейчас. Герб Саттер,
глава Комитета ISO по стандартизации С++, специалист в области архитектуры программного обеспечения на С++ в Microsoft
Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Было бы неплохо, если бы имелся способ написания функций наподобие addName
, таких, чтобы lvalue копировались, rvalue перемещались, при этом (в исходном тексте и объектном коде) имелась бы только одна функция и при этом можно было избежать неприятностей, связанных с универсальными ссылками. И такой способ есть. Все, что от вас требуется, — забыть об одном из первых правил, с которыми вы познакомились как программист на С++. Это правило, гласящее, что следует избегать передачи пользовательских типов по значению. Для параметров наподобие newName
в функциях наподобие addName
передача по значению может быть вполне разумной стратегией.
Перед тем как начать выяснение, почему передача по значению может быть хорошим решением для newName
и addName
, посмотрим, как она может быть реализована:
class Widget {
public:
void addName( std::stringnewName) // lvalue или
{ names.push_back(std::move(newName)); } // rvalue;
// перемещаем его
};
Единственной неочевидной частью этого кода является применение std::move
к параметру newName
. Обычно std::move
используется с rvalue-ссылками, но в данном случае мы знаем, что (1) newName
представляет собой объект, полностью независимый от того, что передает вызывающая функция, так что изменение newName
не влияет на вызывающую функцию, и (2) это последнее применение newName
, так что его перемещение никак не влияет на остальную часть функции.
Тот факт, что существует только одна функция addName
, поясняет, как мы избегаем дублирования кода — как исходного, так и объектного. Мы не используем универсальную ссылку, так что данный подход не ведет к увеличению заголовочных файлов, странным неприятностям или непонятным сообщениям об ошибках. Но что можно сказать об эффективности такого дизайна? Мы же выполняем передачу по значению . Не слишком ли она дорога?
В С++98 можно держать пари, что так и есть. Независимо от того, что передает вызывающая функция, параметр newName
создается с помощью копирующего конструктора . Однако в С++11 newName
будет создаваться с помощью копирующего конструирования только для lvalue. В случае rvalue этот объект создается с помощью перемещающего конструктора . Вот, взгляните:
Widget w;
…
std::string name("Bart");
w.addName(name); // Вызов addName с lvalue
…
w.addName(name + "Jenne"); // Вызов addName с rvalue
// (см. ниже)
В первом вызове addName
(при передаче name
) параметр newName
инициализируется значением lvalue. Поэтому объект newName
создается путем копирования, так же, как это было бы в С++98. Во втором вызове newName
инициализируется объектом std::string
, полученным в результате вызова оператора operator+
для std::string
(т.e. выполнения операции добавления). Этот объект представляет собой rvalue, и newName
, таким образом, создается перемещением.
Итак, lvalue копируются, а rvalue перемещаются, как мы и хотели. Здорово, правда? Здорово, но есть несколько моментов, которые следует иметь в виду. Для облегчения понимания вспомним три рассмотренные версии функции addName
:
class Widget { // Подход 1: перегрузка для lvalue и rvalue
public:
void addName( const std::string&newName)
{ names.push_back(newName); }
void addName(std::string&& newName)
{ names.push_back(std::move(newName)); }
…
private:
std::vector names;
};
class Widget { // Подход 2: применение универсальной ссылки
public:
template
void addName( T&& newName)
{ names.push_back(std::forward(newName)); }
};
class Widget { // Подход 3: передача по значению public:
void addName( std::stringnewName)
{ names.push_back(std::move(newName)); }
};
Я буду говорить о первых двух версиях как о “подходе с передачей ссылки”, поскольку они обе передают параметры по ссылке.
Вот два сценария вызова, которые мы рассмотрели:
Widget w;
…
std::string name("Bart");
w.addName(name); // Передача lvalue
…
w.addName(name + "Jenne"); // Передача rvalue
Давайте теперь рассмотрим стоимость (в операциях копирования и перемещения) добавления имени в Widget
для приведенных сценариев и каждой из трех рассмотренных реализаций addName
. Мы будем игнорировать оптимизирующие возможности компиляторов по удалению копирований и перемещений, поскольку такая оптимизация зависит от контекста и компилятора и с практической точки зрения на суть анализа не влияет.
• Перегрузка. Независимо от передачи lvalue или rvalue аргумент вызывающей функции связан со ссылкой по имени newName
. Это ничего не стоит в смысле операций копирования и перемещения. В перегрузке для lvalue newName
копируется в Widget::names
. В перегрузке для rvalue объект перемещается. Итоговая стоимость: одно копирование lvalue, одно перемещение для rvalue.
• Применение универсальной ссылки. Как и в случае перегрузки, аргумент вызывающей функции связан со ссылкой newName
. Эта операция бесплатна.
Благодаря использованию std::forward lvalue-аргументы std::string
копируются в Widget::names
, в то время как rvalue-apryмeнты std::string
перемещаются. Итоговая стоимость для аргументов std::string
такая же, как и при перегрузке: одно копирование для lvalue, одно перемещение для rvalue.
В разделе 5.3 поясняется, что если вызывающая функция передает аргумент, отличный от std: :string
, он будет передан в конструктор std::string
, и это может привести к нулевому количеству копирований и перемещений std::string
. Таким образом, функции, получающие универсальные ссылки, оказываются уникально эффективными. Однако это не влияет на выполняемый нами анализ, так что для простоты мы будем предполагать, что вызывающая функция всегда передает аргументы std::string
.
• Передача по значению. Независимо от передачи lvalue или rvalue должен быть сконструирован параметр newName
. Если передано lvalue, это стоит одно копирование, если передано rvalue — одно перемещение. В теле функции newName
безусловно перемещается в Widget::names
. Итоговая стоимость, таким образом, равна одному копированию и одному перемещению для lvalue и двум перемещениям для rvalue. По сравнению с подходом с передачей ссылки мы получаем одно лишнее перемещение как для lvalue, так и для rvalue.
Взглянем еще раз на название раздела:
Рассмотрите передачу по значению для копируемых параметров, которые легко перемещаются и всегда копируются.
Оно сформулировано таким образом не без причины. Точнее, не без четырех причин.
1. Вы должны всего лишь рассмотреть использование передачи по значению. Да, при этом требуется написать всего лишь одну функцию. Да, при этом в объектном коде генерируется только одна функция. Да, вы избегаете проблем, связанных с применением универсальных ссылок. Но стоимость этого решения выше, чем стоимость альтернативных вариантов, и, как вы увидите далее, в некоторых случаях есть стоимость, которую мы еще не рассматривали.
Читать дальшеИнтервал:
Закладка: