Просматривая видеозаписи с недавно прошедшей конференции С++Now я наткнулся на один интересный момент (подводный камень). В конце одного из выступлений докладчик приводит следующий код:
struct s { ... }; s f() { const s r; ...; return r; }
и спрашивает, что произойдет с объектом r при возврате из функции?
Предположим, что у структуры s есть и конструктор копирования, и конструктор перемещения, а функция f содержит код, препятствующий применению RVO. Что же произойдет с объектом r при возврате из функции? Если объект создан без спецификатора const, то при возврате из функции он будет перемещен, а если создан с const — скопирован. Подобное поведение замечено в GCC и в Clang (в Visual Studio не проверял). Что это: баг или ожидаемое поведение? Если объект сразу же будет уничтожен, почему не переместить его?
Со времен С++03 многие из нас привыкли к тому, что временные объекты и константы — это почти одно и тоже. И это логично, ведь поведение и тех, и других было схоже: они позволяли вызывать только функции, принимающие аргументы по значению или константной ссылке (или просто присваивать свои значения таким типам):
void f1(int a) { } void f2(int& a) { } void f3(const int& a) { } int g() { return 0; } int main() { f1(g()); // OK f2(g()); // compile error f3(g()); // OK const int a = 0; f1(a); // OK f2(a); // compile error f3(a); // OK }
В C++11 ситуация изменилась: теперь мы можем изменять временные объекты с помощью rvalue ссылок. Но не константы. Согласно стандарту, любое изменение объектов, объявленных со спецификатором const (будь то применение const_cast или извращений с указателями), приводит к неопределенному поведению (undefined behavior). Получается, что если бы компилятор сгенерировал код, использующий конструктор перемещения, то привел бы программу в состояние неопределенного поведения, что недопустимо. Иными словами, const-correctness стоит у компилятора в более высоком приоритете, чем подобная оптимизация. Все-таки С++ — это строго типизированный язык и любые неявные приведения ссылок и указателей от const типа к обычному — запрещены, например: const A a -> A&&.
Рассмотрим следующий код:
void f1(int& a) { } void f2(const int& a) { } int main() { int a1 = 0; f1(a1); // OK int a2 = 0; f2(a2); // OK const int ca1 = 0; f1(ca1); // compile error const int ca2 = 0; f2(ca2); // OK }
Стандарт С++ не позволяет неявно снимать квалификаторы с объектов. Если некоторый тип А можно привести как к A&, так и к const A&, то const A можно привести только к const A&. Теперь заменим lvalue ссылки на rvalue ссылки, добавив вызовы std::move при передачи параметров в функции… Именно! Мы не можем приводить const A a к A&&, но можем к const A&&. Если написать такой конструктор, то компилятор использует его для возврата константных переменных из функций, однако смысла в этом небольше, чем от обычного конструктора копирования. Поэтому такой тип ссылок обычно не используется.
Заключение
Как оказывается, некоторые рекомендуемые практики не сочетаются. В С++ постоянно приходится то тут, то там следить, как бы не запнуться об очередной подводный камень. Теперь нужно следить еще и за константами, чтобы они не усложнили Вам жизнь. Ведь никто не застрахован от такого кода (и даже если Вы рассчитываете на RVO и думаете, что данный случай Вам не важен, то можете потратить много времени на поиски причины, почему не компилируется этот код, если в структуре s нет копирующего конструктора):
struct s { ... }; s foo(); s bar() { const auto r = foo(); if (r.check_something()) throw std::exception(); return r; }
И даже в этом коде есть копирование объекта (еще один подводный камень):
s bar() { auto&& r = foo(); ...; return r; }
Предпочитайте такой вариант:
s bar() { auto r = foo(); ...; return r; }
Или, только если Вы дорожите каждым конструктором перемещения:
s bar() { auto&& r = foo(); ...; return std::move(r); }
Примечание
Не путайте предыдущий пример со следующим кодом, возвращающим rvalue ссылку:
s&& bar() { auto&& r = foo(); ...; return std::move(r); }
Никогда так не делайте, здесь возвращается ссылка на уже уничтоженный объект.
И да пребудет с Вами Сила!
ссылка на оригинал статьи http://habrahabr.ru/post/183454/
Добавить комментарий