Скотт Мейерс - Эффективный и современный С++. 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::bind
для эмуляции перемещающего захвата рассмотрим пример кода С++ 14, который мы видели ранее и который создает std::unique_ptr
в замыкании:
auto func = [ pw = std::make_unique()] // Как и ранее,
{ return pw->isValidated() // создает pw
&& pw->isArchived(); }; // в замыкании
А вот как выглядит его эмуляция на С++11:
auto func = std::bind(
[]( const std::unique_ptr& pw)
{ return pw->isValidated()
&& pw->isArchived(); },
std::make_unique()
);
Забавно, что я показываю, как использовать std::bind
для обхода ограничений лямбда-выражений в С++11, поскольку в разделе 6.4 я выступаю как сторонник применения лямбда-выражений вместо std::bind
. Однако в данном разделе поясняется, что в С++11 имеются ситуации, когда может пригодиться std::bind
, и это одна из них. (В С++14 такие возможности, как инициализирующий захват и параметры auto
, устраняют такие ситуации.)
• Для перемещения объектов в замыкания используется инициализирующий захват С++14.
• В С++11 инициализирующий захват эмулируется с помощью написания классов вручную или применения std::bind
.
6.3. Используйте параметры decltype
для auto&&
для передачи с помощью std::forward
Одной из самых интересных возможностей С++14 являются обобщенные лямбда-выражения — лямбда-выражения, в спецификации параметров которых используется ключевое слово auto
. Реализация этой возможности проста: operator()
в классе замыкания лямбда-выражения является шаблоном. Например, для лямбда-выражения
auto f = []( autox){ return normalize(x); };
оператор вызова функции класса замыкания имеет следующий вид:
class SomeCompilerGeneratedClassName {
public:
template // См. возвращаемый тип auto
auto operator()( Tх) const // в разделе 1.3
{ return normalize(x); }
… // Прочая функциональность
}; // класса замыкания
В этом примере единственное, что делает лямбда-выражение с параметром x
, — это передает его функции normalize
. Если normalize
рассматривает значения lvalue не так, как значения rvalue, это лямбда-выражение написано некорректно, поскольку оно всегда передает функции normalize
lvalue (параметр x
), даже если переданный в лямбда-выражение аргумент представляет собой rvalue.
Корректным способом написания лямбда-выражения является прямая передача x
в normalize
. Это требует внесения в код двух изменений. Во-первых, x
должен быть универсальной ссылкой (см. раздел 5.2), а во-вторых, он должен передаваться в normalize
с использованием std::forward
(см. раздел 5.3). Концептуально это требует тривиальных изменений:
auto f = [](auto &&x)
{ return normalize( std::forward (x )); };
Однако между концепцией и реализацией стоит вопрос о том, какой тип передавать в std::forward
, т.e. вопрос определения того, что должно находиться там, где я написал “ ???
”.
Обычно, применяя прямую передачу, вы находитесь в шаблонной функции, принимающей параметр типа T
, так что вам надо просто написать std::forward
. В обобщенном лямбда-выражении такой параметр типа T
вам недоступен. Имеется T
в шаблонизированном операторе operator()
в классе замыкания, сгенерированном лямбда-выражением, но сослаться на него из лямбда-выражения невозможно, так что это никак не помогает.
В разделе 5.6. поясняется, что если lvalue-apryмeнт передается параметру, являющемуся универсальной ссылкой, то типом этого параметра становится lvalue-ссылка. Если же передается rvalue, параметр становится rvalue-ссылкой. Это означает, что вне лямбда-выражения мы можем определить, является ли переданный аргумент lvalue или rvalue, рассматривая тип параметра x
. Ключевое слово decltype
дает нам возможность сделать это (см. раздел 1.3). Если было передано lvalue, decltype(x)
даст тип, являющийся lvalue-ссылкой. Если же было передано rvalue, decltype(x)
даст тип, являющийся rvalue-ссылкой.
В разделе 5.6 поясняется также, что при вызове std::forward
соглашения требуют, чтобы для указания lvalue аргументом типа была lvalue-ссылка, а для указания rvalue — тип, не являющийся ссылкой. В нашем лямбда-выражении, если x привязан к lvalue, decltype(x)
даст lvalue-ссылку. Это отвечает соглашению. Однако, если x
привязан к rvalue, decltype(x)
вместо типа, не являющегося ссылкой, даст rvalue-ссылку. Но взглянем на пример реализации std::forward
в С++14 из раздела 5.6:
template // В пространстве
T&& forward(remove_reference_t& param) // имен std
{
return static_cast(param);
}
Если клиентский код хочет выполнить прямую передачу rvalue типа Widget
, он обычно инстанцирует std::forward
типом Widget
(т.e. типом, не являющимся ссылочным), и шаблон std::forward
дает следующую функцию:
Widget&& forward( Widget& param) // Инстанцирование для
{ // std::forward, когда
return static_cast< Widget&&>(param); // T является Widget
}
Но рассмотрим, что произойдет, если код клиента намерен выполнить прямую передачу того же самого rvalue типа Widget
, но вместо следования соглашению об определении T
как не ссылочного типа определит его как rvalue-ссылку, т.e. рассмотрим, что случится, если T
определен как Widget&&
. После начального инстанцирования std::forward
и применения std::remove_reference_t
, но до свертывания ссылок (еще раз обратитесь к разделу 5.6) std::forward
будет выглядеть следующим образом:
Widget&&&& forward( Widget¶m) // Инстанцирование
{ // std::forward при
return static_cast< Widget&&&&>(param); // T, равном Widget&&
} // (до сворачивания ссылок)
Применение правила сворачивания ссылок о том, что rvalue-ссылка на rvalue-ссылку становится одинарной rvalue-ссылкой, приводит к следующему инстанцированию:
Widget&& forward(Widget& param) // Инстанцирование
{ // std::forward при
return static_cast(param); // T, равном Widget&&
} // (после сворачивания ссылок)
Если вы сравните это инстанцирование с инстанцированием, получающимся в результате вызова std::forward
с T
, равным Widget
, то вы увидите, что они идентичны. Это означает, что инстанцирование std::forward
типом, представляющим собой rvalue-ссылку, дает тот же результат, что и инстанцирование типом, не являющимся ссылочным.
Это чудесная новость, так как decltype(x)
дает rvalue-ссылку, когда в качестве аргумента для параметра x нашего лямбда-выражения передается rvalue. Выше мы установили, что, когда в наше лямбда-выражение передается lvalue, decltype(х)
дает соответствующий соглашению тип для передачи в std::forward
, и теперь мы понимаем, что для rvalue decltype(x)
дает тип для передачи std::forward
, который не соответствует соглашению, но тем не менее приводит к тому же результату, что и тип, соответствующий соглашению. Так что как для lvalue, так и для rvalue передача decltype(x)
в std::forward
дает нам желаемый результат. Следовательно, наше лямбда-выражение с прямой передачей может быть записано следующим образом:
Интервал:
Закладка: