Скотт Мейерс - Эффективный и современный С++. 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 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
if (std::all_of(begin(container), end(container),
[&](const auto&value) // C++14
{ return value % divisor == 0; }))
Одним из способов решения нашей проблемы с локальной переменной divisor
может быть применение режима захвата по умолчанию по значению, т.e. мы можем добавить лямбда-выражение к filters
следующим образом:
filters.emplace_back( // Теперь
[=](int value) // divisor
{ return value % divisor == 0; } // не может
); // зависнуть
Для данного примера этого достаточно, но в общем случае захват по умолчанию по значению не является лекарством от висящих ссылок, как вам могло бы показаться. Проблема в том, что если вы захватите указатель по значению, то скопируете его в замыкания, возникающие из лямбда-выражения, но не сможете предотвратить освобождение объекта, на который он указывает (и соответственно, повисания), внешним кодом.
“Этого не может случиться! — возразите вы. — После прочтения главы 4 я работаю только с интеллектуальными указателями. Обычные указатели используют только несчастные программисты на С++98”. Это может быть правдой, но это не имеет значения, потому что на самом деле вы используете обычные указатели, а они могут быть удалены. Да, в современном стиле программирования на С++ в исходном коде это незаметно, но это так.
Предположим, что одна из задач, которые могут решать Widget
, — добавление элементов в контейнер фильтров:
class Widget {
public:
… // Конструкторы и т.п.
void addFilter() const; // Добавление элемента в filters
private:
int divisor; // Используется в фильтре
};
Widget::addFilter
может быть определен следующим образом:
void Widget::addFilter() const {
filters.emplace_back(
[=](int value) { return value % divisor == 0; }
);
}
Для блаженно непосвященных код выглядит безопасным. Лямбда-выражение зависит от divisor
, но режим захвата по умолчанию по значению гарантирует, что divisor
копируется в любое замыкание, получающееся из лямбда-выражения, так ведь? Нет. Совершенно не так. Ужасно не так! Смертельно не так!
Захваты применяются только к нестатическим локальным переменным (включая параметры), видимым в области видимости, в которой создано лямбда-выражение. В теле Widget::addFilter
переменная divisor
не является локальной переменной, это — член-данные класса Widget. Она не может быть захвачена. Если отменить режим захвата по умолчанию, код компилироваться не будет:
void Widget::addFilter() const {
filters.emplace_back( // Ошибка! divisor недоступна!
[](int value) { return value % divisor == 0; }
);
}
Кроме того, если сделана попытка явного захвата divisor
(по значению или по ссылке — значения не имеет), захват не компилируется, поскольку divisor
не является локальной переменной или параметром:
void Widget::addFilter() const {
filters.emplace_back( // Ошибка! Нет захватываемой
[divisor](int value) // локальной переменной divisor!
{ return value % divisor == 0; }
);
}
Если захват по умолчанию по значению не захватывает divisor
, а без захвата по умолчанию по значению код не компилируется, то что же происходит?
Объявление связано с неявным использованием обычного указателя: this
. Каждая нестатическая функция-член получает указатель this
, и вы используете этот указатель всякий раз при упоминании члена-данных этого класса. В любой функции-члене Widget
, например, компиляторы внутренне заменяют каждое использование divisor
на this->divisor
. В версии Widget::addFilter
с захватом по умолчанию по значению
void Widget::addFilter() const {
filters.emplace_back(
[=](int value) { return value % divisor == 0; }
);
}
this->divisor
захватывается указатель this
объекта Widget
, а не divisor
. Компиляторы рассматривают этот код так, как будто он написан следующим образом:
void Widget::addFilter() const {
auto currentObjectPtr = this;
filters.emplace_back(
[currentObjectPtr](int value)
{ return value % curentObjectPtr->divisor == 0;}
);
}
Понимание этого равносильно пониманию того, что жизнеспособность замыканий, вытекающих из этого лямбда-выражения, связана со временем жизни объекта Widget
, копии указателя this
которого в них содержатся. В частности, рассмотрим код, который в соответствии с главой 4 использует только интеллектуальные указатели:
using FilterContainer = // Как и ранее
std::vector>;
FilterContainer filters; // Как и ранее
void doSomeWork() {
auto pw = // Создание Widget;
std::make_unique(); // std::make_unique см. в
// разделе 4.4
pw->addFilter(); // Добавление фильтра
// с Widget::divisor
} // Уничтожение Widget; filters хранит висячий указатель!
Когда выполняется вызов doSomeWork
, создается фильтр, зависящий от объекта Widget
, созданного std::make_unique
, т.e. фильтр, который содержит копию указателя на этот Widget
, — указатель this
объекта Widget
. Этот фильтр добавляется в filters
, но по завершении работы doSomeWork
объект Widget
уничтожается, так как std::unique_ptr
управляет его временем жизни (см. раздел 4.1). С этого момента filters
содержит элемент с висячим указателем.
Эта конкретная проблема может быть решена путем создания локальной копии члена-данных, который вы хотите захватить, и захвата этой копии:
void Widget::addFilter() const {
auto divisorCopy = divisor; // Копирование
filters.emplace_back( // члена-данных
[divisorCopy](int value) // Захват копии
{ return value % divisorCopy== 0; } // Ее использование
);
);
Чтобы быть честным, скажу, что при таком подходе захват по умолчанию по значению также будет работать:
void Widget::addFilter() const {
auto divisorCopy = divisor; // Копирование
filters.emplace_back( // члена-данных
[=](int value) // Захват копии
{ return value % divisorCopy== 0; } // Ее использование
);
}
но зачем искушать судьбу? Режим захвата по умолчанию делает возможным случайный захват this
, когда вы думаете, в первую очередь, о захвате divisor
.
В С++14 имеется лучший способ захвата члена-данных, заключающийся в использовании обобщенного захвата лямбда-выражения (см. раздел 6.2):
void Widget::addFilter() const {
filters.emplace_back( // С++14:
[divisor = divisor](int value) // Копирование divisor
// в замыкание
{ return value % divisor== 0; } // Использование копии
);
}
Однако такого понятия, как режим захвата по умолчанию для обобщенного захвата лямбда-выражения, не существует, так что даже в С++14 остается актуальным совет данного раздела — избегать режимов захвата по умолчанию.
Читать дальшеИнтервал:
Закладка: