Скотт Мейерс - Эффективный и современный С++. 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 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Имейте в виду, что весь этот раздел — основы универсальных ссылок — является лож… простите, абстракцией. Лежащая в основе истина, известная как свертывание ссылок (reference collapsing), является темой раздела 5.6. Но истина не делает абстракцию менее полезной. Понимание различий между rvalue-ссылками и универсальными ссылками поможет вам читать исходные тексты более точно (“Этот T&&
связывается только с rvalue или с чем угодно?”), а также избегать неоднозначностей при общении с коллегами (“Здесь я использовал универсальную ссылку, а не rvalue-ссылку…”). Оно также поможет вам в понимании смысла разделов 5.3 и 5.4, которые опираются на указанное различие. Так что примите эту абстракцию, погрузитесь в нее… Так же как законы Ньютона (технически не совсем корректные) обычно куда полезнее и проще общей теории относительности Эйнштейна (“истины”), так и понятие универсальных ссылок обычно предпочтительнее для работы, чем детальная информация о свертывании ссылок.
• Если параметр шаблона функции имеет тип T&&
для выводимого типа T
или если объект объявлен с использованием auto&&
, то параметр или объект является универсальной ссылкой.
• Если вид объявления типа не является в точности type&&
или если вывод типа не имеет места, то type&&
означает rvalue-ссылку.
• Универсальные ссылки соответствуют rvalue-ссылкам, если они инициализируются значениями rvalue. Они соответствуют lvalue-ссылкам, если они инициализируются значениями lvalue.
5.3. Используйте std::move
для rvalue-ссылок, а std::forward
— для универсальных ссылок
Rvalue-ссылки связываются только с объектами, являющимися кандидатами для перемещения. Если у вас есть параметр, представляющий собой rvalue-ссылку, вы знаете , что связанный с ним объект может быть перемещен:
class Widget {
Widget(Widget&& rhs); // rhs, определенно, ссылается на
… // объект, который можно перемещать
};
В этом случае вы захотите передавать подобные объекты другим функциям таким образом, чтобы разрешить им использовать преимущества “правосторонности”. Способ, которым это можно сделать, — привести параметры, связываемые с такими объектами, к rvalue. Как поясняется в разделе 5.1, std::move
не просто это делает, это та задача, для которой создана эта функция:
class Widget {
public:
Widget(Widget&& rhs) // rhs является rvalue-ссылкой
: name( std::move(rhs.name)),
p( std::move(rhs.p))
{ … }
…
private:
std::string name;
std::shared_ptr p;
};
С другой стороны, универсальная ссылка (см. раздел 5.2) может быть связана с объектом, который разрешено перемещать. Универсальные ссылки должны быть приведены к rvalue только тогда, когда они были инициализированы с помощью rvalue. В разделе 5.1 разъясняется, что именно это и делает функция std::forward
:
class Widget f
public:
template
void setName(T&& newName) // newName является
{ name = std::forward(newName);} // универсальной ссылкой
};
Короче говоря, rvalue-ссылки при их передаче в другие функции должны быть безусловно приведены к rvalue (с помощью std::move
), так как они всегда связываются с rvalue, а универсальные ссылки должны приводиться к rvalue при их передаче условно (с помощью std::forward
), поскольку они только иногда связываются с rvalue.
В разделе 5.1 поясняется, что можно добиться верного поведения rvalue-ссылок и с помощью std::forward
, но исходный текст при этом становится многословным, подверженным ошибкам и неидиоматичным, так что вы должны избегать применения std::forward
с rvalue-ссылкам. Еще худшей является идея применения std::move
к универсальным ссылкам, так как это может привести к неожиданному изменению значений lvalue (например, локальных переменных):
class Widget {
public:
template
void setName( T&&newName) // Универсальная ссылка.
{ name = std::move(newName); } // Компилируется, но это
… // очень плохое решение!
private:
std::string name;
std::shared_ptr p;
};
std::string getWidgetName(); // Фабричная функция
Widget w;
auto n = getWidgetName(); // n - локальная переменная
w.setName(n); // Перемещение n в w!
// Значение n теперь неизвестно
Здесь локальная переменная n
передается функции w.setName
. Вызывающий код можно простить за предположение о том, что эта функция по отношению к n
является операцией чтения. Но поскольку setName
внутренне использует std::move
для безусловного приведения своего ссылочного параметра к rvalue, значение n
может быть перемещено в w.name
, и n
вернется из вызова setName
с неопределенным значением. Этот вид поведения может привести программиста к отчаянию — если не к прямому насилию.
Можно возразить, что setName
не должен был объявлять свой параметр как универсальную ссылку. Такие ссылки не могут быть константными (см. раздел 5.2), но setName
, безусловно, не должен изменять свой параметр. Вы могли бы указать, что если перегрузить setName
для константных значений lvalue и rvalue, то этой проблемы можно было бы избежать, например, таким образом:
class Widget {
public:
void setName( const std::string&newName) // Устанавливается
{ name = newName; } // из const lvalue
void setName( std::string&&newName) // Устанавливается
{ name = std::move(newName); } // из rvalue
};
В данном случае это, безусловно, сработает, но у метода есть и недостатки. Во-первых, требуется вводить исходный текст большего размера (две функции вместо одного шаблона). Во-вторых, это может быть менее эффективным. Например, рассмотрим следующее применение setName
:
w.setName("Adela Novak");
При наличии версии setName
, принимающей универсальную ссылку, функции setName
будет передан строковый литерал "Adela Novak"
, в котором он будет передан оператору присваивания для std::string
внутри w
. Таким образом, член-данные name
объекта w
будет присвоен непосредственно из строкового литерала; никакого временного объекта std::string
создаваться не будет. Однако в случае перегруженных версий setName
будет создан временный объект std::string
, с которым будет связан параметр функции setName
, и этот временный объект std::string
будет перемещен в член-данные объекта w
. Таким образом, вызов setName
повлечет за собой выполнение одного конструктора std::string
(для создания временного объекта), одного перемещающего оператора присваивания std::string
(для перемещения newName
в w.name
) и одного деструктора std::string
(для уничтожения временного объекта). Это практически наверняка более дорогостоящая последовательность операций, чем вызов только одного оператора присваивания std::string
, принимающего указатель const char*
. Дополнительная стоимость может варьироваться от реализации к реализации, и стоит ли беспокоиться о ней, зависит от приложения и библиотеки; однако, скорее всего, в ряде случаев замена шаблона, получающего универсальную ссылку, парой функций, перегруженных для lvalue- и rvalue-ссылок, приведет к дополнительным затратам времени выполнения.
Интервал:
Закладка: