Эффективный и современный С++
42 рекомендации по использованию С++11 и С++14
Скотт Мейерс
Дарле,
выдающемуся черному лабрадору-ретриверу
Отзывы о книге “
Вас интересует С++? Современный С++ (т.е. C++11/C++14) — это гораздо больше чем простое внесение косметических изменений в старый стандарт. Учитывая новые возможности языка, это скорее его переосмысление. Вам нужна помощь в его освоении? Тогда перед вами именно та книга которая вам нужна. Что касается С++, то Скотт Мейерс был и остается синонимом точности, качества и удовольствия от чтения.
Трудно получить достаточный опыт и стать экспертом. Не менее трудно стать настоящим учителем, способным просто и ясно донести сложный материал до ученика. Если вы читаете эту книгу, то вы знаете человека, который объединяет оба эти качества. Книга
Когда человек с более чем двадцатилетним опытом работы с С++ берется рассказать, как получить максимальную отдачу от современного С++ (рассказывая как о лучших подходах, так и о возможных ловушках, которых следует избегать), я настоятельно рекомендую внимательно прочесть его книгу! Я, определенно, узнал из нее много нового!
Бьярне Страуструп — создатель С++ — сказал: “С++11 выглядит как новый язык программирования”: Книга
Скотт умеет добраться до самой сути любой технической проблемы. Книги серии
Я люблю С++, он десятилетиями был моей рабочей лошадкой. А с новыми копытами эта лошадка стала еще сильнее и привлекательнее, чем я мог ранее себе представить. Но при больших изменениях всегда встает вопрос “Когда и как пользоваться всем этим богатством?” Как всегда, книга Скотта Мейерса компетентно и исчерпывающе отвечает на поставленный вопрос.
Отличное чтение для перехода к современному С++ — новинки языка C++11/14 описаны наряду с C++98, разделение содержимого книги на разделы позволяет легко найти интересующую тему, а в конце каждого раздела приведены итоговые рекомендации. Книга интересна и полезна для программистов на С++ всех уровней.
Если вы переходите с С++98/03 на C++11/14, вам нужна точная практичная информация, которую вам предоставляет Скотт Мейерс в книге
Об авторе
Скотт Мейерс — один из ведущих мировых экспертов по С++, широко востребованный как инструктор, консультант и докладчик на разных конференциях. Более чем 20 лет книги Скотта Мейерса серии aristeia.com.
Об изображении на обложке
На обложке книги
Голубь распространен в равнинных лесах восточной Австралии, муссонных лесах северной части Австралии, а также на малых Зондских островах и Молуккских островах Индонезии. Рацион голубя состоит из различных фруктов наподобие инжира (который он поедает целиком), пальм и лоз. Еще одним источником пищи голубя является камфорный лавр, большое вечнозеленое дерево. Они питаются парами, небольшими стайками или поодиночке в тропических лесах, обычно утром или поздно вечером. Для питья они используют воду из листьев или росу, но не воду с земли.
Пестрый голубь в Новом Южном Уэльсе считается видом, находящимся под угрозой исчезновения из-за изменения среды обитания — лесозаготовок, очистки и улучшения земель, а также вырубки камфорного лавра без адекватной альтернативы.
Многие из животных, представленных на обложках O'Reilly, находятся под угрозой исчезновения. Все они имеют очень важное значение для нашего мира. Чтобы узнать больше о том, как вы можете помочь им, посетите сайт animals.oreilly.com.
Изображение для обложки взято из
Благодарности
Я начал исследовать то, что тогда было известно как C++0x (зарождающийся С++11), в 2009 году. Я опубликовал множество вопросов в группе новостей Usenet comp.std.c++ и благодарен членам этого сообщества (в особенности Дэниелу Крюглеру (Daniel Krugler)) за их очень полезные сообщения. В последние годы с вопросами по С++11 и С++14 я обращался к Stack Overflow, и я многим обязан этому сообществу за его помощь в понимании тонкостей современного С++.
В 2010 году я подготовил материалы для учебного курса по С++0x (в конечном итоге опубликованные как
Я не могу указать источники всей информации в этой книге, но некоторые из них непосредственно повлияли на мою книгу. Применение в разделе 1.4 неопределенного шаблона для получения информации о типе от компилятора было предложено Стивеном T. Лававеем, а Мэтт П. Дзюбински (Matt P. Dziubinski) обратил мое внимание на Boost.TypeIndex. В разделе 2.1 пример unsigned std::vector<int>::size_type взят из статьи Андрея Карпова (Andrey Karpov) от 28 февраля 2010 года “std::pair<std::string, int>/std::pair<const std::string, int> в том же разделе книги взят из сообщения “constexpr-функций в С++14 включает информацию, которую я получил от Рейна Халберсма (Rein Halbersma). Раздел 3.10 основан на презентации Герба Саттера на конференции const и mutable”. Совет в разделе 4.1, гласящий, что фабричная функция должна возвращать std::unique_ptr, основан на статье Герба Саттера “GotW# 90 Solution: Factories” от 13 мая 2013 года. fastLoadWidget в разделе 4.2 получен из презентации Герба Саттера “std::unique_ptr и неполных типов в разделе 4.5 использованы статья Герба Саттера от 27 ноября 2011 года “Matrix в разделе 5.3 основан на письме Дэвида Абрахамса (David Abrahams). Комментарий Джо Аргонна (Joe Argonne) от 8 декабря 2012 года к материалу из блога “std::bind в C++11. Пояснения в разделе 7.3 проблемы с неявным отключением в деструкторе std::thread взяты из статьи Ганса Бехма (Hans-J. Boehm) “
Проверка черновиков технической книги является длительной и критичной, но совершенно необходимой работой, и мне повезло, что так много людей были готовы за нее взяться. Черновики этой книги были официально просмотрены такими специалистами, как Кассио Нери (Cassio Neri), Нейт Кёль (Nate Kohl), Герхард Крейцер (Gerhard Kreuzer), Леон Золман (Leor Zolman), Барт Вандевойстин (Bart Vandewoestyne), Стивен T. Лававей (Stephan T. Lavavej), Невин Либер (Nevin “:-)” Liber), Речел Ченг (Rachel Cheng), Роб Стюарт (Rob Stewart), Боб Стигалл (Bob Steagall), Дамьен Уоткинс (Damien Watkins), Брэдли Нидхам (Bradley E. Needham), Рейнер Гримм (Rainer Grimm), Фредрик Винклер (Fredrik Winkler), Джонатан Уокели (Jonathan Wakely), Герб Саттер (Herb Sutter), Андрей Александреску (Andrei Alexandrescu), Эрик Ниблер (Eric Niebler), Томас Беккер (Thomas Becker), Роджер Орр (Roger Orr), Энтони Вильямc (Anthony Williams), Майкл Винтерберг (Michael Winterberg), Бенджамин Хахли (Benjamin Huchley), Том Кирби-Грин (Tom Kirby-Green), Алексей Никитин (Alexey А. Nikitin), Вильям Дилтрай (Willie Dealtry), Хуберт Мэттьюс (Hubert Matthews) и Томаш Каминьски (Tomasz Kaminski). Я также получил отзывы ряда читателей с помощью сервисов O'Reilly's Early Release EBooks и Safari Books Online's Rough Cuts, посредством комментариев в моем блоге (The
Черновики цифровых версий книги были подготовлены Герхардом Крейцером (Gerhard Kreuzer), Эмиром Вильямсом (Emyr Williams) и Брэдли Нидхэмом (Bradley E. Needham).
Мое решение ограничить длину строки кода 64 символами (максимум для правильного отображения на печати, а также на различных цифровых устройствах при разной ориентации и конфигурации шрифтов) было основано на данных, предоставленных Майклом Махером (Michael Maher).
С момента первой публикации я исправил ряд ошибок и внес некоторые усовершенствования, предложенные такими читателями, как Костас Влахавас (Kostas Vlahavas), Даниэль Алонсо Алеман (Daniel Alonso Alemany), Такатоши Кондо (Takatoshi Kondo), Бартек Сургот (Bartek Szurgot), Тайлер Брок (Tyler Brock), Джай Ципник (Jay Zipnick), Барри Ревзин (Вагу Revzin), Роберт Маккейб (Robert McCabe), Оливер Брунс (Oliver Bruns), Фабрис Ферино (Fabrice Ferino), Дэнез Джонитис (Dainis Jonitis), Петр Валашек (Petr Valasek) и Барт Вандевойстин (Bart Vandewoestyne). Большое спасибо всем им за помощь в повышении точности и ясности изложенного материала.
Эшли Морган Вильямc (Ashley Morgan Williams) готовила отличные обеды у себя в Lake Oswego Pizzicato. Им (и ей) нет равных.
И более двадцати лет моя жена, Нэнси Л. Урбано (Nancy L. Urbano), как обычно во время моей работы над новой книгой, терпит мою раздражительность и оказывает мне всемерную поддержку. В ходе написания книги постоянным напоминанием о том, что за пределами клавиатуры есть другая жизнь, служила мне наша собака Дарла.
Введение
Если вы — опытный программист на языке программирования С++, как, например, я, то, наверное, первое, о чем вы подумали в связи с С++11, — “Да, да, вот и он — тот же С++, только немного улучшенный”. Но познакомившись с ним поближе, вы, скорее всего, были удивлены количеством изменений. Объявления auto, циклы for для диапазонов, лямбда-выражения и rvalue-ссылки изменили лицо С++, — и это не говоря о новых возможностях параллельности. Произошли и идиоматические изменения. 0 и typedef уступили место nullptr и объявлениям псевдонимов. Перечисления получили области видимости. Интеллектуальные указатели стали предпочтительнее встроенных; перемещение объектов обычно предпочтительнее их копирования.
Даже без упоминания С++14 в С++11 есть что поизучать.
Что еще более важно, нужно очень многое изучить, чтобы использовать новые возможности
Информация в книге разбита на отдельные разделы, посвященные тем или иным рекомендациям. Вы хотите разобраться в разных видах вывода типов? Или хотите узнать, когда следует (а когда нет) использовать объявление auto? Вас интересует, почему функция-член, объявленная как const, должна быть безопасна с точки зрения потоков, как реализовать идиому Pimpl с использованием std::unique_ptr, почему следует избегать режима захвата по умолчанию в лямбда-выражениях или в чем различие между std::atomic и volatile? Ответы на эти вопросы вы найдете в книге. Более того, эти ответы не зависят от платформы и соответствуют стандарту. Это книга о
Разделы книги представляют собой рекомендации, а не жесткие правила, поскольку рекомендации имеют исключения. Наиболее важной частью каждого раздела является не предлагаемая в нем рекомендация, а ее обоснование. Прочитав раздел, вы сможете сами определить, оправдывают ли обстоятельства вашего конкретного проекта отход от данной рекомендации. Истинная цель книги не в том, чтобы рассказать вам, как надо поступать или как поступать не надо, а в том, чтобы обеспечить вас более глубоким пониманием, как та или иная концепция работает в С++11 и С++14.
Чтобы мы правильно понимали друг друга, важно согласовать используемую терминологию, начиная, как ни странно это звучит, с термина “С++”. Есть четыре официальные версии С++, и каждая именуется с использованием года принятия соответствующего стандарта ISO: С++98, C++03, C++11 и С++14. С++98 и C++03 отличаются один от другого только техническими деталями, так что в этой книге обе версии я называю как С++98. Говоря о С++11, я подразумеваю и С++11, и С++14, поскольку С++ 14 является надмножеством С++11. Когда я пишу “С++14”, я имею в виду конкретно С++14. А если я просто упоминаю С++, я делаю утверждение, которое относится ко всем версиям языка.
| Использованный термин | Подразумеваемая версия |
|---|---|
| С++ | Все |
| С++98 | С++98 и С++03 |
| С++11 | С++11 и С++14 |
| С++14 | С++14 |
В результате я мог бы сказать, что в С++ придается большое значение эффективности (справедливо для всех версий), в С++98 отсутствует поддержка параллелизма (справедливо только для С++98 и С++03), С++11 поддерживает лямбда-выражения (справедливо для C++11 и С++14) и С++14 предлагает обобщенный вывод возвращаемого типа функции (справедливо только для С++14).
Наиболее важной особенностью С++11, вероятно, является семантика перемещения, а основой семантики перемещения является отличие
Полезной эвристикой для выяснения, является ли выражение lvalue, является ответ на вопрос, можно ли получить его адрес. Если можно, то обычно это lvalue. Если нет, это обычно rvalue. Приятной особенностью этой эвристики является то, что она помогает помнить, что тип выражения не зависит от того, является ли оно lvalue или rvalue. Иначе говоря, для данного типа Т можно иметь как lvalue типа Т, так и rvalue типа Т. Особенно важно помнить это, когда мы имеем дело с параметром rvalue ссылочного типа, поскольку сам по себе параметр является lvalue:
class Widget {
public:
Widget(Widget&& rhs); //
// и имеет ссылочный тип rvalue
};
Здесь совершенно корректным является взятие адреса rhs в перемещающем конструкторе Widget, так что rhs представляет собой lvalue, несмотря на то что его тип — ссылка rvalue. (По сходным причинам все параметры являются lvalue.)
Этот фрагмент кода демонстрирует несколько соглашений, которым я обычно следую.
• Имя класса — Widget. Я использую слово Widget, когда хочу сослаться на произвольный пользовательский тип. Если только мне не надо показать конкретные детали класса, я использую имя Widget, не объявляя его.
• Я использую имя параметра rhs (“right-hand side”, правая сторона). Это предпочитаемое мною имя параметра для
Matrix operator+(const Matrix& lhs, const Matrix& rhs);
Я надеюсь, для вас не станет сюрпризом, что lhs означает “left-hand side” (левая сторона).
• Я использую специальное форматирование для частей кода или частей комментариев, чтобы привлечь к ним ваше внимание. В перемещающем конструкторе Widget выше я подчеркнул объявление rhs и часть комментария, указывающего, что rhs представляет собой lvalue. Выделенный код сам по себе не является ни плохим, ни хорошим. Это просто код, на который вы должны обратить внимание.
• Я использую “…”, чтобы указать “здесь находится прочий код”. Такое “узкое” троеточие отличается от широкого “...”, используемого в исходных текстах шаблонов с переменным количеством параметров в С++11. Это кажется запутанным, но на самом деле это не так. Вот пример.
template<typename... Ts> // Эти троеточия
void processVals(const Ts&... params) // в исходном
{ // тексте С++
… // Это троеточие озна-
// чает какой-то код
}
Объявление processVals показывает, что я использую ключевое слово typename при объявлении параметров типов в шаблонах, но это просто мое личное предпочтение; вместо него можно использовать ключевое слово class. В тех случаях, когда я показываю код, взятый из стандарта С++, я объявляю параметры типа с использованием ключевого слова class, поскольку так делает стандарт.
Когда объект инициализирован другим объектом того же типа, новый объект является
void someFunc(Widget w); // Параметр w функции someFunc
// передается по значению
Widget wid; // wid - объект класса Widget
someFunc(wid); // В этом вызове someFunc w
// является копией wid, созданной
// копирующим конструктором
someFunc(std::move(wid)); // В этом вызове SomeFunc w
// является копией wid, созданной
// перемещающим конструктором
Копии rvalue в общем случае конструируются перемещением, в то время как копии lvalue обычно конструируются копированием. Следствием является то, что если вы знаете только то, что объект является копией другого объекта, то невозможно сказать, насколько дорогостоящим является создание копии. В приведенном выше коде, например, нет возможности сказать, насколько дорогостоящим является создание параметра w, без знания того, какое значение передано функции someFunc — rvalue или lvalue. (Вы также должны знать стоимости перемещения и копирования Widget.)
В вызове функции выражения, переданные в источнике вызова, являются someFunc, показанном выше, аргументом является wid. Во втором вызове аргументом является std::move(wid). В обоих вызовах параметром является w. Разница между аргументами и параметрами важна, поскольку параметры являются lvalue, но аргументы, которыми они инициализируются, могут быть как rvalue, так и lvalue. Это особенно актуально во время
Хорошо спроектированные функции
Говоря о operator(). Другими словами, это объект, действующий, как функция. Иногда я использую термин в несколько более общем смысле для обозначения чего угодно, что может быть вызвано с использованием синтаксиса вызова функции, не являющейся членом (т.е. functionName(arguments)). Это более широкое определение охватывает не только объекты, поддерживающие operator(), но и функции и указатели на функции в стиле С. (Более узкое определение происходит из С++98, более широкое — из C++11.) Дальнейшее обобщение путем добавления указателей на функции-члены дает то, что известно как
Функциональные объекты, создаваемые с помощью лямбда-выражений, известны как
Многие сущности в С++ могут быть как объявлены, так и определены.
extern int x; // Объявление объекта
class Widget; // Объявление класса
bool func(const Widget& w); // Объявление функции
enum class Color; // Объявление перечисления
// с областью видимости
// (см. раздел 3.4)
int x; // Определение объекта
class Widget { // Определение класса
};
bool func(const Widget& w)
{ return w.size() < 10; } // Определение функции