Скотт Мейерс - Эффективный и современный С++. 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 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
std::multiset names; // Глобальная структура данных
void logAndAdd(const std::string& name) {
auto now = // Получение текущего времени
std::chrono::system_clock::now();
log(now, "logAndAdd"); // Создание журнальной записи
names.emplace(name); // Добавление nаmе в глобальную
} // структуру данных; emplace
// см. в разделе 8.2
Этот код не является неразумным, но он не такой эффективный, каким мог бы быть. Рассмотрим три потенциальных вызова:
std::string petName("Darla");
logAndAdd( petName); // lvalue типа std::string
logAndAdd( std::string("Persephone")); // rvalue типа std::string
logAndAdd( "Patty Dog"); // Строковый литерал
В первом вызове параметр паше функции logAndAdd
связывается с переменной petName
. Внутри logAndAdd
параметр name в конечном итоге передается в вызов names.emplace
. Поскольку name
является lvalue, он копируется в names
. Избежать этого копирования невозможно, так как lvalue(petName)
передается в функцию logAndAdd
.
Во втором вызове параметр name
связывается с rvalue (временный объект std::string
, явно созданный из строки "Persephone"
). Параметр name
сам по себе является lvalue, так что он копируется в names
, но мы отдаем себе отчет, что, в принципе, это значение может быть перемещено в names
. В этом вызове мы платим за копирование, но мы должны быть способны сделать то же с помощью перемещения.
В третьем вызове параметр name опять связывается с rvalue, но в этот раз со временным объектом std::string
, который неявно создается из "Patty Dog"
. Как и во втором вызове, name
копируется в names
, но в этот раз аргумент, изначально переданный в logAndAdd
, был строковым литералом. Если бы строковый литерал непосредственно передавался в emplace
, в создании временного объекта std::string
не было бы необходимости вообще. Вместо этого функция emplace
использовала бы строковый литерал для создания объекта std::string
непосредственно в std::multiset
. Таким образом, в этом третьем вызове мы платим за копирование std::string
, при том что нет причин платить даже за перемещение, не говоря уже о копировании.
Неэффективность второго и третьего вызовов logAndAdd
можно устранить, переписав эту функцию так, чтобы она принимала универсальную ссылку (см. раздел 5.2) и, согласно разделу 5.3, передавала ее с помощью std::forward
функции emplace
. Результат говорит сам за себя:
template
void logAndAdd(T&& name) {
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace( std::forward(name));
}
std::string petName("Darla"); // Как и ранее
logAndAdd( petName); // Как и ранее, копирова-
// ние lvalue в multiset
logAndAdd( std::string("Persephone")); // Перемещение rvalue
// вместо копирования
logAndAdd( "Patty Dog"); // Создание std::string
// в multiset вместо
// копирования временного
// std::string
Ура, получена оптимальная эффективность!
Если бы это был конец истории, мы могли бы остановиться и гордо удалиться, но я не сказал вам, что клиенты не всегда имеют непосредственный доступ к именам, требующимся logAndAdd
. Некоторые клиенты имеют только индекс, который logAndAdd
использует для поиска соответствующего имени в таблице. Для поддержки таких клиентов выполняется перегрузка функции logAndAdd
:
std::string nameFromIdx(int idx); // Возвращает имя,
// соответствующее idx
void logAndAdd( int idx) // Новая перегрузка
{
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace( nameFromIdx(idx));
}
Разрешение перегрузки работает, как и следовало ожидать:
std::string petName("Darla"); // Как и ранее
logAndAdd(petName); // Как и ранее, эти вызовы
logAndAdd(std::string("Persephone")); // приводят к использова-
logAndAdd("Patty Dog"); // нию перегрузки для T&&
logAndAdd(22); // Вызов int-перегрузки
На самом деле разрешение работает, как ожидается, только если вы не ожидаете слишком многого. Предположим, клиент имеет переменную типа short
, хранящую индекс, и передает ее функции logAndAdd
:
short nameIdx;
… // Дает значение переменной nameIdx
logAndAdd(nameIdx); // Ошибка!
Комментарий в последней строке, может быть, не слишком понятен, так что позвольте мне пояснить, что же здесь произошло.
Имеется две перегрузки logAndAdd
. Одна из них, принимающая универсальную ссылку, может вывести тип T
как short
, тем самым приводя к точному соответствию. Перегрузка с параметром int
может соответствовать аргументу short
только с повышением. Согласно обычным правилам разрешения перегрузки точное соответствие побеждает соответствие с повышением, так что вызывается перегрузка для универсальной ссылки.
В этой перегрузке параметр name
связывается с переданным значением типа short
. Таким образом, name
передается с помощью std::forward
функции-члену emplace
объекта names(std::multiset)
, которая, в свою очередь, послушно передает его конструктору std::string
. Но конструктора std::string
, который принимал бы значение short
, не существует, так что вызов конструктора std::string
в вызове multiset::emplace
в вызове logAndAdd
неудачен. Все дело в том, что перегрузка для универсальной ссылки точнее соответствует аргументу типа short
, чем перегрузка для int
.
Функции, принимающие универсальные ссылки, оказываются самыми жадными в С++. Они в состоянии выполнить инстанцирование с точным соответствием практически для любого типа аргумента (несколько видов аргументов, для которых это не так, описаны в разделе 5.8). Именно поэтому сочетание перегрузки и универсальной ссылки почти всегда является плохой идеей: перегрузка для универсальных ссылок годится для гораздо большего количества типов аргументов, чем обычно ожидает разработчик перегрузок.
Простой способ свалиться в эту яму — написать конструктор с прямой передачей. Небольшое изменение функции logAndAdd
демонстрирует эту проблему. Вместо написания свободной функции, которая принимает либо std::string
, либо индекс, который можно использовать для поиска std::string
, представим себе класс Person
с конструкторами, которые выполняют те же действия:
class Person {
public:
template
explicit Person(T&& n) // Конструктор с прямой передачей
: name(std::forward(n)){} // инициализирует члены-данные
explicit Person(int idx) // Конструктор с параметром int
: name(nameFromIdx(idx)) {}
…
private:
std::string name;
};
Как и в случае с logAndAdd
, передача целочисленного типа, отличного от int
(например, std::size_t
, short
, long
и т.п.), будет вызывать перегрузку конструктора для универсальной ссылки вместо перегрузки для int
, и это будет вести к ошибкам компиляции. Однако проблема гораздо хуже, поскольку в Person имеется больше перегрузок, чем видит глаз. В разделе 3.11 поясняется, что при соответствующих условиях С++ будет генерировать как копирующие, так и перемещающие конструкторы, и это так и будет, даже если класс содержит шаблонный конструктор, который при инстанцировании в состоянии дать сигнатуру копирующего или перемещающего конструктора. Если таким образом генерируются копирующий и перемещающий конструкторы для Person
, класс Person
будет выглядеть, по сути, следующим образом:
Интервал:
Закладка: