Аннотация к «Effective Modern C++» Скотта Майерса. Часть 2

image Продолжение предыдущего поста.В этой части мы будем рассматривать не столько технические изменения в С++, сколько новые подходы к разработке и возможности которые дают новые средства языка. Предыдущий пост был с моей точки зрения просто затянувшимся вступлением, тогда как здесь можно вволю подискутировать.Лямбда-выражения — вишенка на тортеКак ни удивительно это звучит, но лямбда-выражения не принесли в язык новой функциональности (в оригинале — expressive power). Тем не менее, их все более широкое применение стремительно меняет стиль языка, легкость создания обьектов-функций на лету вдохновляет и осталось лишь дождаться повсеместного распространения C++14 (который как-бы уже есть, но как-бы еще и не совсем), где лямбды достигли полного расцвета. Начиная с С++14 лямбда-выражения предполагаются абсолютной заменой std: bind, не остается ни одной реальной причины его использовать. Во-первых, и это самое главное, лямбды легче читаются и яснее выражают мысль автора. Я не буду приводить здесь достаточно громоздкий код для иллюстрации, в оригинале у Майерса его предостаточно. Во-вторых, лямбда-выражения как правило работают быстрее. Дело в том что std: bind захватывает и хранит указатель на функцию, поэтому компилятор имеет мало шансов встроить (inline) ее, тогда как согласно стандарту оператор вызова функции в замыкании (closure) содержащем лямбда-выражение обязан быть встроенным, поэтому компилятору остается совсем немного работы чтобы встроить все лямбда-выражение в точке вызова. Есть еще пара менее значимых причин, но они сводятся в основном к недостаткам std: bind и я их опущу.Главная опасность при работе с лямбда-выражениями — способы захвата переменных (capture mode). Наверное излишне говорить что вот такой код потенциально опасен [&](…) { … }; Если лямбда-замыкание переживет любую из захваченных локальных переменных, мы получаем висячую ссылку (dangling reference) и в результате undefined behavior. Это настолько очевидно, что я даже примеры кода приводить не буду. Стилистически чуть-чуть лучше вот такой вариант: [&localVar](…) { … }; мы по крайней мере контролируем какие именно переменные захвачены, а также имеем напоминание перед глазами. Но проблемы это никоим образом не решает.Код где лямбда генерируется на лету std: all_of (container.begin (), container.end (), [&]() { … }); конечно безопасен, хотя Майерс даже тут предупреждает об опасности копипаста. В любом случае хорошей привычкой будет всегда явно перечислять переменные захватываемые по ссылке и не использовать [&].Но это еще не конец, давайте захватывать все по значению [=]() { *ptr=… }; Опаньки, указатель захватился по значению и что, нам от этого легче? Но и это еще не все… std: vector> filters;

class Widget { … void addFilter () const { filters.emplace_back ([=](int value) { return value % divisor == 0; }); } private: int divisor; }; ну здесь-то все совершенно безопасно надеюсь? Зря надеетесь. Wrong. Completely wrong. Horribly wrong. Fatally wrong. (@ScottMeyers)

Дело в том что лямбда захватывает локальные переменные в области видимости, ей нет никакого дела до того что divisor принадлежит классу Widget, нет в области видимости — захвачен не будет. Вот такой код для сравнения вообще не компилируется: … void addFilter () const { filters.emplace_back ([divisor](int value) { return value % divisor == 0; }); … }; Так что же захватывается? Ответ прост, захватывается this, divisor в коде на самом деле трактуется компилятором как this→divisor и если Widget выйдет из области видимости мы возвращаемся к предыдущему примеру с повисшим указателем. По счастью, для этой проблемы решение есть: std: vector> filters;

class Widget { … void addFilter () const { auto localCopy=divisor; filters.emplace_back ([=](int value) { return value % localCopy == 0; }); } private: int divisor; }; сделав локальную копию переменной класса, мы позволяем нашей лямбде захватить ее по значению.Возможно вы будете плакать, но и это еще не все! Немного ранее я упоминал что лямбды захватывают локальные переменные в области видимости, они могу также использовать (т.е. зависеть от) статических обьектов (static storage duration), однако они их не захватывают. Пример:

static int divisor=…;

filters.emplace_back ([=](int value) { return value % divisor == 0; });

++divisor; //, а вот после этого начнутся чудеса Лямбда не захватывает статическую переменную divisor, а ссылается на нее, можно сказать (хоть это и не совсем корректно) что статическая переменная захватывается по ссылке. Все бы ничего, но значок [=] в определении лямбды нам кагбэ намекал что все захватывается по значению, полученное лямбда замыкание самодостаточно, его можно хранить тысячу лет и передавать из функции в функцию и оно будет работать как новенькое… Обидно получилось. А знаете какой из этого вывод? Не надо злоупотреблять значком [=] точно так же как и [&], не ленитесь перечислять все переменные и будет вам счастье.Вот теперь можете смеяться, на этот раз все…Причем действительно все, больше про лямбда-выражения сказать по сути нечего, можно брать и пользоваться. Тем не менее я расскажу в оставшейся части о дополнениях которые принес С++14, эта область еще слабо документирована и это одно из немногих мест где изменения действительно глубокие.Одна вещь, которая меня с самого начала безумно раздражала в C++11 лямбда-выражениях — отсутствие возможности переместить (move) переменную внутрь замыкания. С подачи ТР1 и boost мы внезапно осознали что мир вокруг нас полон обьектов которые нельзя копировать, std: unique_ptr<>, std: atomic<>, boost: asio: socket, std: thread, std: future — число таких обьектов стремительно растет после того как была осознана простая идея: то что не поддается естественному копированию копировать и не надо, зато переместить можно всегда. И вдруг такое жестокое разочарование, новый инструмент языка эту конструкцию не поддерживает.

Конечно, этому существует разумное обьяснение А в какой момент осуществлять само перемещение? А как быть с копированием самого замыкания? etc

однако осадочек остается. И вот, наконец появляется C++14 который эти проблемы решает неожиданным и элегантным способом: захват с инициализацией (init capture). class Widget { … }; auto wptr=std: make_unique();

auto func=[wptr=std: move (wptr)]{ return wptr→…(); };

func (); Мы создаем в заголовке лямбды новую локальную переменную в которую и перемещаем требуемый параметр. Обратите внимание на два интересных момента. Первый — имена переменных совпадают, это не обязательно, но удобно и безопасно, потому что их области видимости не пресекаются. Переменная слева от знака = определена только внутри тела лямбды, тогда как переменная в выражении справа от = определена извне и не определена внутри. Второй — мы заодно получили возможность захватывать целые выражения, а не только переменные как раньше. Вполне законно и разумно написать вот так: auto func=[wptr=std: make_unique()] { return wptr→…(); }; func (); Что же однако делать тем кто остается на C++11? Скажу честно, до выхода этой книги я не раз пытал интернет и получал неизменно один ответ — в C++11 это невозможно, однако решение есть и оно описано прямо в следующем абзаце (интернету следовало бы в этом месте покраснеть). Вспомните с чего начался этот раздел: «лямбда-выражения не принесли в язык новой функциональности», все что они делают можно с тем же успехом сделать руками. Как-то вот так: class PeudoLambda { explicit PeudoLambda (std: unique_ptr&& w) : wptr (std: move (w)) {} bool operator ()() const { return wptr→…(); } private: std: unique_ptr()); func (); Если же все таки не хочется работать руками, а хочется использовать лямбда-выражения то… решение есть все равно, просто придется вместо самодельного класса использовать std: bind, это как раз тот случай когда его использование в C++11 остается оправданным.Трюк выполняется на два приема: на раз наш обьект перемещается в обьект созданный std: bind, потом на счет два лямбде передается ссылка на этот обьект. std: vector data; auto func=[data=std: move (data)] { … }; // C++14 way auto func=std: bind ( [](std: vector& data) { … }, // C++11 trick std: move (data) ); не так конечно элегатно, но ведь работает же, как временная мера вполне пойдет.Второе, и главное почему все ждали C++14 с нетерпением, вводятся шаблонные лямбда-выражения (generic lambdas).

auto f=[](auto x) { return func (x); }; используя auto в декларации параметров мы получаем возможность передавать произвольные значения в лямбда-замыкание. А как оно устроено под капотом? Ничего магического class PseudoLambda { … template auto operator ()(T x) const { return func (x); } }; просто соответствующий оператор () обьявлен шаблоном и принимает любые типы. Однако приведенный пример не совсем корректен, лямбда в этом примере всегда будет передавать x как lvalue, даже если параметр к лямбде бык передан как rvalue. Здесь полезно было бы перечитать первую часть поста про выведение типов, а еще лучше соответствующую главу в книге. Я однако сразу же приведу окончательный вариант: auto f=[](auto&& x) { return func (std: forward(x)); }; //, а вот такой вариант еще лучше // , да, да лямбды могут принимать переменное число аргументов auto f=[](auto&&… x) { return func (std: forward(x)…); }; Ну вот наверное и все про лямбды. Я предсказываю что скоро появится множество изящного кода использующего еще может быть неосознанные возможности лямбда-выражений, нечто похожее на взрывной рост метапрограммирования. Запасаюсь попкорном.

Умные указатели, Smart pointers Опасная тема, на эту тему исписаны горы бумаги, тысячи юных комментаторов с горящими глазами безжалостно забанены на всевозможных форумах. Однако доверимся Майерсу, он обещает, дословно «Я сосредоточусь на информации которой часто нет в документации API, заслуживающих внимания примерах использования, анализу скорости исполнения, etc. Владение этой информацией означает разницу между использованием и эффективным использованием умных указателей»

Под такие гарантии я пожалуй рискну сунуться в этот бушующий холивар.В современном языке начиная с C++11 существует три вида умных указателей, std: unique_ptr, std: shared_ptr<> и std: weak_ptr<>, все они работают с обьектами размещенными на куче, но каждый из них реализует свою модель управления своими данными.std: unique_ptr<> единолично владеет своим обьектом и убивает его когда умирает сам. Да, он может быть только один. std: shared_ptr<> разделяет владение с данными с другими собратьями, обьект живет до тех пор пока жив хотя бы один из указателей. std: weak_ptr<> сравнительно малоизвестен, он расширяет std: shared_ptr<> используя более тонкие механизмы управления. Кратко, он ссылается на обьект не захватывая его, пользуется, но не владеет. std: shared_ptr<> самый известный из этой триады, однако, поскольку он использует внутренние счетчики ссылок на обьект, он заметно проигрывает по эффективности обычным указателям. К счастью, благодаря одновременному появлению атомарных переменных, операции с std: shared_ptr<> абсолютно потокопезопасны и почти так же быстры как с обычными указателями. Тем не менее, при создании умного указателя память на куче должна быть выделена не только для хранения самого обьекта, но и для управляющего блока, в котором хранятся счетчики ссылок и ссылка на деаллокатор. Выделение этой памяти сильно влияет на скорость исполнения и это очень веская причина использовать std: make_shared<>(), а не создавать указатель руками, последняя функция выделяет память и для обьекта и для управляющего блока за один раз и поэтому сильно выигрывает по скорости. Тем не менее по размеру std: shared_ptr<> занимает естественно больше в два раза чем простой указатель, не считая выделенной на куче памяти.std: shared_ptr<> также поддерживает нестандартные деаллокаторы памяти (обычный delete по умолчанию), и такой приятный дизайн: тип указателя не зависит от наличия деаллокатор и его сигнатуры.

std: shared_ptr p1(new Widget (…), customDeleter); std: shared_ptr p2=std: make_shared(…); эти два указателя имеют один и тот же тип и могут быть присвоены друг другу, переданы в одну и ту же функцию, помещены вместе в контейнер, очень гибко хотя память на куче и приходится выделять. К сожалению std: make_shared нестандартные деаллокаторы не поддерживает, приходится создавать руками.Еще хочу заметить что в C++ std: shared_ptr<> реализует концепцию сборщика мусора, обьект будет уничтожен когда на него перестанет ссылаться последний из его указателей, причем, в отличие от сборщиков в других языках, деструктор вызывается немедленно и детерменистично.Очевидно что при работе с разделяемым указателем существует только одна опасность — передать сырой указатель в конструкторы двух разных классов Widget *w=new Widget; std: shared_ptr p1(w); std: shared_ptr p2(w); В этом случае будет создано два управляющих блока со своими счетчиками ссылок и неизбежно рано или поздно вызовутся два деструктора. Ситуация избегается просто, не надо никогда использовать сырые указатели на обьект, в идеале всегда лучше использовать std: make_shared<>(). Однако существует важное исключение std: vector> widgetList; class Widget { … void save () { widgetList.emplace_back (this); } }; Здесь Widget хочет вставить себя в некоторый внешний контейнер для чего ему необходимо создать разделяемый указатель. Однако обьект класса не знает, и не может знать в принципе, был ли он уже передан под управление другого указателя, если да, то этот код неизбежно упадет. Для разрешения ситуации был создан CRTP класс std: enable_shared_from_this std: vector> widgetList; class Widget: public td: enable_shared_from_this { … void save () { widgetList.emplace_back (shared_from_this ()); } }; Магическим образом унаследовання функция shared_from_this () найдет и использует контрольный блок класса, это эквивалентно копированию умного указателя, если он был создан, или его созданию если не был.В общем это великолепный класс — мощный, компактный, предельно быстрый для своей функциональности. Единственное в чем его можно упрекнуть — его вездесущесть, его используют там где надо, там где не надо и там где ни в коем случае не надо.std: unique_ptr<> наоборот, сильно недоиспользуется по моему мнению. Только взгляните на его характеристики — он занимает ровно столько же памяти сколько и обычный указатель, его инструкции практически всегда транслируются в такой же точно код что и для обычного указателя. Это намекает на то что неплохо бы задуматься над своим дизайном, если по смыслу указатель — единственный владелец обьекта в каждый отдельно взятый момент, то std: unique_ptr<> — несомненно лучший кандидат. Конечно, думать в терминах надо делиться/не надо делиться пока еще не очень привычно, но ведь и к систематическому использованию const тоже когда-то приходилось привыкать.Еще несколько плюшек в комплекте, std: unique_ptr<> свободно конвертируется (естественно перемещается) в std: shared_ptr<>, обратно естественно никак, даже если счетчик ссылок равен 1.

auto del=[](base_type* p) { …; delete p; }; template std: unique_ptr factory (Ts&&… args) { std: unique_ptr p (nullptr, del); … p.reset (new derived_type (std: forward(args)…)); // фабрике естественно возвращять std: unique_ptr<> // она возвращает уникальный обьект и знать не хочет // как он будет использоваться return p; }

// пользователь однако хочет делиться этим обьектом // ну и на здоровье std: shared_ptr=factory (…args…); Из примера видна еще одна плюшка — std: unique_ptr свободно конвертируется в std: unique_ptr. Вообще абстрактная фабрика это естественный паттерн применения для этого типа указателя.Еще плюшек, может инициализироваться неполным типом (pimpl idiom), удобный вариант для любителей этого стиля. А еще, если обьявить std: unique_ptr<> константой, его невозможно передать наверх из области видимости где он был создан.Можно так же создавать std: unique_ptr<> с нестандартным деаллокатором памяти, однако в отличие от std: shared_ptr<> это влияет на его тип: void del1(Widget*); void del2(Widget*);

std: unique_ptr p1; std: unique_ptr p2; здесь p1 и p2 — два разных типа, это цена которую приходится платить за минимальный размер обьекта, кроме того, нестандартные деаллокаторы также не поддерживаются std: make_unique.И наконец, плюшка которой пользоваться крайне не рекомендуется, уникательные указатели могут иметь форму std: unique_ptr которая может хранить массив, нестандатные деаллокаторы опять же с ней несовместимы, да и вообще, в C++ хватает других типов контейнеров.Это самый яркий пример типа для которого копирование не имеет смысла по дизайну, перемещение же наоборот — естественная операция.std: weak_ptr<> является надстройкой над std: shared_ptr<> и, как ни странно это звучит, он сам не может быть разыменован, т.е. данные на которые он указывает недоступны. Две почти единственные операции над ним — это конструктор из разделяемого указателя и конвертация в разделяемый указатель

auto sp=std: make_shared(); std: weak_ptr wp (sp); … std: shared_ptr sp1=wp; // 1 std: shared_ptr sp2=wp.lock (); // 2 То есть мы можем создать слабый указатель из разделяемого, некоторое время его хранить, а потом попытаться снова получить из него разделяемый указатель. Зачем? Дело в том что std: weak_ptr<> не владеет обьектом на который указывает, ни единолично как std: unique_ptr<>, ни кооперативно как std: shared_ptr<>. Он всего лишь ссылается на этот обьект и дает нам возможность атомарно получить контроль над ним, то есть создать новый разделяемый указатель владеющий этим обьектом. Естественно, к этому времени обьект может уже быть уничтожен, отсюда два варианта в примере. Первый, через конструктор, выбросит в этом случае исключение std: bad_weak_ptr. Второй вариант более мягкий, std: weak_ptr<>:: lock () вернет пустой разделяемый указатель, но его надо не забыть проверить перед использованием.И для чего это надо? Например для хранения загружаемых обьектов во временном контейнере-кэше, мы не хотим вечно хранить обьект в памяти, но и не хотим загружать обьект каждый раз когда он понадобится, поэтому мы храним ссылки в виде слабых указателей и при запросе, если указатель повис, подгружаем обьект снова, а если обьект уже был загружен и еще не удален, используем полученный разделяемый указатель. Бывает еще ситуация когда два обьекта должны ссылаться друг на друга, ссылаться чем? Ответ «простыми указателями» не принимается поскольку их фундаментальное ограничение — не знать что там с обьектом на который указываешь, а если мы используем разделяемые указатели то эта парочка навсегда зависнет в памяти, удерживая счетчики ссылок друг друга. Выход — использовать shared_ptr на одном конце и weak_ptr на другом, тогда ничто не удержит первый обьект от уничтожения, а второй будет способен это определить.В общем, хотя слабые указатели и не являются очень распространенными, они делают картину законченной и закрывают все дырки в применении.На этом позвольте считать эту главу закрытой, про умные указатели действительно написано очень много, даже пересказывая Майерса вряд ли я добавлю что-то новое.

Универсальные ссылки На эту тему Майерс непрерывно пишет в блоге и читает лекции последние два года. Сама концепция настолько удивительна что меняет привычные приемы программирования и работы с обьектами. Тем не менее, есть в ней что-то такое что плохо укладывается в голове, по крайней мере в моей, поэтому я прошу разрешения у сообщества начать с самого начала, от элементарных основ. Заодно может и свои мысли в порядок приведу.Вспомним что такое lvalue и rvalue, термины которым чуть ли не больше лет чем C++. lvalue определить сравнительно просто: это все что может стоять слева от знака присвоения '='. Например, все имена автоматически являются lvalue. А вот с rvalue гораздо туманнее, это как бы все что не является lvalue, то есть может стоять справа от '=', но не может стоять слева. А что не может стоять слева? Огласите весь список пожалуйста: ну во-первых естественно литералы, а во-вторых результаты выражений не присвоенные никакой переменной, тот промежуточный результат в выражении х=a+b; который был вычислен и будет присвоен х (это легче осознать если думать об х не как о целом, а как о сложном классе, очевидно сначала правая часть вычисляется и только потом вызывается оператор присвоения ее х). Однако, помните: имя — всегда lvalue, это действительно важно.Дальше произошло осознание (уже довольно давно, но уже не в такие доисторические времена) что с временными обьектами можно не церемониться во время копирования, жить им все равно осталось пару машинных тактов, а так же то что на этом можно сильно сэкономить. Например, копирование std: map — черезвычайно долгая операция, однако std: swap обменяет содержимое двух обьектов практически мгновенно, несмотря на то что ему технически надо для этого выполнить ! три! копирования На самом деле все stl контейнеры хранят указатели на внутренние данные, так что все сводится к обмену указателей. Однако для нас это сейчас не важно, достаточно знать что std: swap работает быстро.

Таким образом, если некая функция возвращает std: map и мы хотим присвоить это значение другой std: map, это будет долгая операция в случае копирования, однако если бы в операторе присвоения внутренне бы вызывался std: swap, возвращение из функции прошло бы мгновенно. Да, в этом случае исходный (временный) обьект остался бы с каким-то неопределенным содержимым, ну и что? Осталась только одна проблема — средствами языка обозначить такие временные обьекты. Так родились rvalue references обозначаеые значком &&. Выражение type&& является отдельным типом, отличным от type, так же как type& и type*, в частности, можно перегружать функции для каждого типа, т.е. создавать отдельный вариант для параметра по значению, по ссылке и по перемещающей ссылке. int x1=0; int&& x2=0; // assigning lvalue to rvalue int&& x3=x1; // error: cannot bind «int» lvalue to «int&&» int&& x4=std: move (x1); // x4 is a name, so it is lvalue here int&& x5=x4; // error: cannot bind «int» lvalue to «int&&» auto&& x6=0; auto&& x7=x1; эти простые примеры легко понять и означают они все одно — type&& означает перемещающую ссылку (rvalue reference) на type. На самом деле я нагло вру Если вам кажется что это все тривиально, скажите в каком примере это не так, просто для самоконтроля.

Однако все снова осложняется, выражение type&& не всегда означает rvalue reference, в выражениях где присутствуют выведение типов (type deduction), то есть или в шаблонах или в выраженях с auto они могут быть как перемещающими ссылками, так и обычными ссылками, Майерс предпочитает называть их универсальными ссылками (universal references). template void f (T&& param); auto&& var2 = var1; в обоих примерах используется выведение типа (вспомните первую главу), тип для переменных param и var2 выводится из фактических параметров. Widget w; f (w); // 1 f (std: move (w)); //2 param — это универсальная ссылка, в первом случае в шаблонную функцию передается lvalue и тип параметра становится обычной ссылкой (lvalue reference) — Widget&. Во втором случае параметр передается как rvalue и тип параметра становится перемещающей сссылкой — Widget&&.Но выражению мало быть шаблоном чтобы выражение T&& было универсальной ссылкой (повторюсь, означать либo T& либо T&&), необходимо еще чтобы само выражение имело строго вид T&&. Вот в таком примере template void f (std: vector&& param); template void f (const T&& param); param всегда означает rvalue reference, если вы попробуете передать имя в качестве параметра, компилятор немедленно вам укажет: «cannot bind lvalue to rvalue» в отличие от предыдущего примера, где он с готовностью обьявлял тип параметра lvalue reference при необходимости. Вообще не каждое T&& внутри шаблона означает универсальную ссылку template class vector { public: void push_back (T&& x); … }; Вот например, здесь push_back не является шаблонной функцией и T&& не используется при выведении типа, тип параметра шаблона выводится ранее, при реализации класса, поэтому эта функциа всегда принимает rvalue.Тем кто уже перешел на C++14 и обобщенные лямбда-выражения придется гораздо чаще встречаться с необходимостью различать rvalue references и universal references потому что параметры типа auto&& рутинно применяются для передачи произвольных параметров в лямбду.Для управления типом ссылок используются две функции, std: move и std: forward, причем ни та ни другая не генерирует ни одной машинной инструкции template decltype (auto) move (T&& param) { return static_cast&&>(param); } template T&& forward (T&& param) { return static_cast(param); } Вспоминая правила выведения типов, видно что std: move применяет модификатор && к результату std: remove_reference_t [C++14] т.е. чистому значению и таким образом всегда возвращает T&&, то есть безусловное приведение к rvalue reference. В отличие от нее, результат std: forward зависит от параметра, возвращает lvalue reference если параметр является lvaluе и rvalue reference в остальных случаях, то есть условный каст.В перемещающем конструкторе в примере ниже мы должны вызвать перемещающий конструктор для параметра name (иначе он будет копироваться), поскольку мы видим что владеющий им класс передан нам как rvalue

class Widget { std: string name; public: … Widget (Widget&& x) : name (std: move (x.name)) {} template void setName (T&& _name) { name=std: forward(_name); } }; Наоборот, в функции класса setName () мы получаем универсальную ссылку как параметр, которая может быть как rvalue так и lvaluе, функция std: move, то эта функция просто портила бы передаваемый ей параметр, оставляя его с неопределенным значением.Маленькая success-story: пусть у нас есть классический C++98 код; std: set names;

void add (const std: string& name) { … names.insert (name); }

std: string name («Виктор Иванович»); add (name); // 1 pass lvalue std: string add (std: string («Вася»)); // 2 pass rvalue std: string add («Тузик»); // 3 pass string literal В первом случае эта функция вызывается оптимально, строка переданная по константной ссылке копируется в контейнер и ничего улучшить здесь невозможно. Во втором вызове мы могли бы переместить временную строку в контейнер, что на порядок эффективнее копирования. В третьем случае идиотизм зашкаливает — мы передаем const char* указатель, который не является строкой, но валидным типом для создания строки, которая и создается. После этого эта временная строка копируется в контейнер. Таким образом мы совершенно напрасно вызываем конструктор и оператор копирования.Теперь посмотрим что нам предлагает новый стандарт взамен: template void add (T&& name) { … names.emplace (std: forward(name)); }

std: string name («Виктор Иванович»); add (name); // 1 pass lvalue std: string add (std: string («Вася»)); // 2 pass rvalue std: string add («Тузик»); // 3 pass string literal В первом вызове мы точно так же копируем параметр, во втором мы вызываем перемещение вместо копирования, а в третьем вообще просто передаем параметр для создания строки в std: set: emplace (). Этот маленький пример показывает насколько более эффективным может быть код при переходе на новый стандарт.Да, в новом стандарте по-прежнему немало подводных камней, в частности приведенный код становится плохо управляемым если мы перегружаем функцию с другим параметром, особенно острой проблема становится при перегрузке конструкторов и идеальной передаче параметров (perfect forwarding). Тем не менее прекрасно что C++ остается динамично развивающимся языком который активно вбирает в себя все новое и хорошее. Про тот же std: move в одной из первых о нем публикаций кто-то из маститых осторожно заметил: «видимо это останется фишкой для разработчиков системных библиотек и не пригодится обычным пользователям языка» (цитата примерная, полагаюсь на память). Однако по накалу обсуждений и числу публикаций видно что C++ не превратился в язык где умное меньшинство разрабатывает инструменты для бессловесного большинства. Так же как и в далеких 19…-х, C++ сообщество активно сует нос и прикладывает руки везде куда можно и куда нельзя, возможно это и определяет современный статус языка лучше всего. Так давайте же поднимем за это Долой пафос, похоже пора закругляться.Многопоточное API Вот про эту главу я пожалуй писать ничего не стану, мне она просто не понравилась. Возможно я нашел у Майерса слабое место, возможно я сам чего-то недопонимаю, но мне гораздо больше нравится другая книга: C++ Concurrency in Action. Читайте сами и решайте.В общем я постарался как мог передать содержание, но до оригинала мне конечно далеко. Скоро выйдет русский перевод, всем советую.

© Habrahabr.ru