Скотт Мейерс - Эффективный и современный С++. 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 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
4.5. При использовании идиомы указателя на реализацию определяйте специальные функции-члены в файле реализации
Если вам приходилось бороться со слишком большим временем построения приложения, вы, вероятно, знакомы с идиомой Pimpl (pointer to implementation, указатель на реализацию). Это методика, при которой вы заменяете члены-данные класса указателем на класс (или структуру) реализации, помещаете в него члены-данные, использовавшиеся в основном классе, и обращаетесь к ним опосредованно через указатель. Предположим, например, что наш Widget
имеет следующий вид:
class Widget { // В заголовочном файле "widget.h"
public:
Widget();
…
private:
std::string name;
std::vector data;
Gadget g1, g2, g3; // Gadget - некий пользовательский тип
};
Поскольку члены-данные Widget
имеют типы std::string
, std::vector
и Gadget
, для компиляции Widget
должны присутствовать соответствующие заголовочные файлы, а это означает, что клиенты Widget
должны включать с помощью директивы #include
заголовочные файлы string
, vector
и gadget.h
. Эти заголовочные файлы увеличивают время компиляции клиентов Widget
, а также делают этих клиентов зависящими от содержимого указанных заголовочных файлов. Если содержимое заголовочного файла изменяется, клиенты Widget
должны быть перекомпилированы. Стандартные заголовочные файлы string
и vector
меняются не слишком часто, но заголовочный файл gadget.h
вполне может оказаться подвержен частым изменениям.
Применение идиомы Pimpl в С++98 могло выполняться с помощью замены членов- данных Widget обычным указателем на объявленную, но не определенную структуру:
class Widget { // Все еще в заголовочном файле "widget.h"
public:
Widget();
~Widget(); // Деструктор необходим (см. ниже)
…
private:
struct Impl; // Объявление структуры реализации
Impl *pImpl; // и указателя на нее
};
Поскольку Widget
больше не упоминает типы std::string
, std::vector
и Gadget
, клиенты Widget
больше не обязаны включать соответствующие заголовочные файлы для этих типов. Это ускоряет компиляцию, а кроме того, означает, что если что-то в заголовочных файлах будет изменено, это не затронет клиенты Widget
.
Тип, который был объявлен, но не определен, называется неполным типом . Widget::Impl
является таким неполным типом. С неполным типом можно сделать очень немногое, но в это немногое входит объявление указателя на него. Идиома Pimpl использует эту возможность.
Первая часть идиомы Pimpl — объявление члена-данных, который представляет собой указатель на неполный тип. Вторая часть заключается в динамическом создании и уничтожении объекта, хранящего члены-данные, использующиеся в исходном классе. Соответствующий код находится в файле реализации, например для Widget
— в файле widget.cpp
:
#include "widget.h" // Файл реализации "widget.cpp"
#include "gadget.h"
#include
#include
struct Widget::Impl { // Определение Widget::Impl
std::string name; // с членами-данными, ранее
std::vector data; // находившимися в Widget
Gadget g1, g2, g3;
};
Widget::Widget() // Создание членов-данных
: pimpl( new Impl) // для данного объекта Widget
{}
Widget::~Widget() // Уничтожение членов-данных
{ delete pImpl;} // для данного объекта
Здесь я привожу директивы #include
, чтобы было ясно, что общие зависимости от заголовочных файлов для std::string
, std::vector
и Gadget
никуда не исчезли и продолжают существовать. Однако эти зависимости перемещены из файла widget.h
(видимого и используемого всеми клиентами класса Widget
) в файл widget.cpp
(видимый и используемый только реализацией Widget
). Я также подчеркнул код динамического выделения и освобождения объекта Impl
. Необходимость освобождения этого объекта при уничтожении Widget
приводит к необходимости деструктора Widget
.
Но я показал код С++98, от которого пахнет пылью вековой… Нет, пылью прошлого тысячелетия. Он использует обычные указатели, обычные операторы new
и delete
, и вообще весь он слишком сырой [15] Непереводимая игра слов, основанная на использовании для обычного указателя названия “raw pointer” (дословно — “сырой указатель”). — Примеч. пер .
. Вся текущая глава построена на идее о том, что интеллектуальные указатели куда предпочтительнее обычных указателей, и если мы хотим динамически создавать объект Widget::Impl
в конструкторе Widget
и должны уничтожать его вместе с Widget
, то для нас отлично подойдет интеллектуальный указатель std::unique_ptr
(см. раздел 4.1). Заменяя обычный указатель pImpl
указателем std::unique_ptr
, мы получим следующий код для заголовочного файла
class Widget { // В файле "widget.h"
public:
Widget();
…
private:
struct Impl;
std:unique_ptr<Impl >pImpl; // Интеллектуальный указатель
}; // вместо обычного
и для файла реализации:
#include "widget.h" // В файле "widget.cpp"
#include "gadget.h"
#include
#include
struct Widget::Impl { // Как и ранее
std::string name;
std::vector data;
Gadget g1, g2, g3;
};
Widget::Widget() // Согласно разделу 4.4
: pImpl( std::make_unique<Impl >()) // создаем std: :unique_ptr
{} // с помощью std::make_unique
Вы заметили, что деструктора Widget
больше нет? Дело в том, что нет никакого кода, который требуется в нем разместить. std::unique_ptr
автоматически удаляет то, на что указывает, когда он сам (указатель std::unique_ptr
) уничтожается, так что нам не требуется ничего удалять вручную. Это одна из привлекательных сторон интеллектуальных указателей: они устраняют необходимость утруждать свои руки вводом кода для освобождения ресурсов вручную.
Этот код компилируется, но, увы, это не самый тривиальный клиент!
#include "widget.h"
Widget w; // Ошибка!
Получаемое вами сообщение об ошибке зависит от используемого компилятора, но в общем случае текст упоминает что-то о применении sizeof
или delete
к неполному типу. Эти операции не входят в число тех, которые можно делать с такими типами.
Эта явная неспособность идиомы Pimpl
использовать std::unique_ptr
вызывает тревогу, поскольку (1) указатели std::unique_ptr
разрекламированы как поддерживающие неполные типы, и (2) идиома Pimpl — один из наиболее распространенных случаев применения std::unique_ptr
. К счастью, этот код легко сделать работающим. Все, что для этого требуется, — понимание причины проблемы.
Проблема возникает из-за кода, который генерируется при уничтожении w
(например, при выходе переменной за пределы области видимости). В этой точке вызывается ее деструктор. Если определение класса использует std::unique_ptr
, мы не объявляем деструктор, так как нам нечего в него поместить. В соответствии с обычными правилами генерации специальных функций-членов компиляторами (см. раздел 3.11) этот деструктор создается вместо нас компилятором. В этот деструктор компилятор вносит код вызова деструктора члена-данных pImpl
класса Widget.pImpl
представляет собой указатель std::unique_ptr
, т.e. указатель std::unique_ptr
, использующий удалитель по умолчанию. Удалитель по умолчанию является функцией, которая применяет оператор delete
к обычному указателю внутри std::unique_ptr
. Однако перед тем как использовать delete
, реализации удалителя по умолчанию в C++11 обычно применяют static_assert
, чтобы убедиться, что обычный указатель не указывает на неполный тип. Когда компилятор генерирует код для деструкции Widget w
, он в общем случае сталкивается с неудачным static_assert
, что и приводит к выводу сообщения об ошибке. Это сообщение обычно связано с точкой, в которой происходит уничтожение w
, поскольку деструктор Widget
, подобно всем генерируемым компиляторами специальным функциям-членам, неявно является inline
. Сообщение часто указывает на строку, в которой создается w
, поскольку она представляет собой исходный текст, явно создающий объект, приводящий впоследствии к неявной деструкции.
Интервал:
Закладка: