#c #c 11
#c #c 11
Вопрос:
До C 11 и в качестве стандартной идиомы программирования временные переменные часто присваивались переменным, чтобы сделать код более чистым. Для небольших типов обычно создается копия, а для больших типов, возможно, ссылка, например:
int a = int_func();
T const amp; obj = obj_func();
some_func( a, obj );
Теперь сравните это с встроенной формой:
some_func( int_func(), obj_func() );
До C 11 это имело почти идентичное семантическое значение. С введением семантики rvalue-reference и move все вышеперечисленное теперь совершенно другое. В частности, принудительно obj
вводя тип T const amp;
, вы удалили возможность использования конструктора перемещения, тогда как вместо этого встроенным типом может быть Tamp;amp;
тип.
Учитывая, что первая является общей парадигмой, есть ли что-нибудь в стандарте, что позволило бы оптимизатору использовать конструктор перемещения в первом случае? То есть, может ли каким-то образом компилятор игнорировать привязку к a T const amp;
и вместо этого рассматривать его как a Tamp;amp;
, или, как я подозреваю, это нарушит правила абстрактной машины?
Вторая часть вопроса, чтобы сделать это правильно в C 11 (без исключения именованных временных объектов), нам нужно каким-то образом объявить правильную rvalue-reference . Мы также можем использовать auto
ключевое слово. Итак, каков правильный способ сделать это? Я предполагаю, что:
autoamp;amp; obj = obj_func();
Комментарии:
1. Вы уверены, что использование именованных временных объектов так же распространено, как вы думаете?
2. Я должен ответить на вопрос Марка… по моему опыту, большинство людей даже не знают, что это вариант, не говоря уже о том, чтобы попытаться воспользоваться им, за исключением неясных обстоятельств.
3. Я практически никогда не видел кода, написанного таким образом.
4. Я бы писал такой код только в том случае, если бы хотел продлить срок службы
obj
за пределами вызоваsome_func()
.5. На самом деле, я считаю
some_func( int_func(), obj_func() )
гораздо более читаемым, чем ваша версия с этими ложными временными значениями. Добавление дополнительных переменных не всегда делает код более читаемым. Кроме того, в вашей версии необходимо учитывать проблемы с жизненным циклом (obj
временное значение будет сохраняться до тех пор, пока сама ссылка не исчезнет, что может изменить наблюдаемое поведение), и вы излишне используете имена из своего пространства имен идентификаторов (не может иметь переменныхa
иobj
больше в той же области и, вероятно, не должно использоваться в той же функции).
Ответ №1:
Часть 1:
Компилятору не разрешается неявно преобразовывать obj
в неконстантное rvalue и, следовательно, использовать конструктор перемещения при вызове some_func
.
Часть 2:
autoamp;amp; obj = obj_func();
Это создаст неконстантную ссылку на временное значение, но оно не будет неявно перемещено при вызове some_func
, поскольку obj
является значением lvalue . Чтобы преобразовать его в rvalue, вы должны использовать std::move
на сайте вызова:
some_func( a, std::move(obj) );
Комментарии:
1. Теперь мне интересно,
autoamp;amp;
действительно ли это продлит срок службы, чтобы иметь возможность вызывать функцию? Кроме того, кажется очень неудачным, что здесь все еще нужно звонитьstd::move
.2. Продление срока службы для ссылок rvalue идентично тому, что для ссылок lvalue . В общем, ссылки rvalue ведут себя точно так же, как ссылки lvalue, за исключением очевидных проблем с привязкой / перегрузкой, для которых были изобретены ссылки rvalue. О необходимости вызова
std::move
: это проблема безопасности. Как только у вас есть имя для объекта, очень легко использовать этот объект более чем в одном месте. Вы не хотите неявно переходить от чего-либо более одного раза. Создание явных перемещений для именованных объектов помогает предотвратить случайные двойные перемещения. Действительно, случайные двойные перемещения — это именно то, почемуstd::auto_ptr
они устарели.
Ответ №2:
Я сомневаюсь, что это можно было бы оптимизировать, поскольку неясно, будете ли вы снова использовать временное значение или нет:
Foo x = get_foo(); // using best-possible constructor and (N)RVO anyway
do_something(x);
do_something_else(x);
//...
Если вы действительно заинтересованы в использовании семантики перемещения где-либо (но обязательно сначала просмотрите профиль, чтобы убедиться, что это действительно важно), вы можете сделать это явным с помощью move
:
Foo y = get_foo();
do_oneoff_thing(std::move(y)); // now y is no longer valid!
Я бы сказал, что если что-то подходит для перемещения, то вы могли бы также выполнить встраивание самостоятельно и обойтись без дополнительной локальной переменной. В конце концов, что хорошего в такой временной переменной, если она используется только один раз? Единственный сценарий, который приходит на ум, — это если последнее использование локальной переменной может использовать семантику перемещения, чтобы вы могли добавить std::move
к окончательному виду. Однако это звучит как опасность для обслуживания, и вам действительно нужна веская причина, чтобы написать это.
Комментарии:
1. Очевидно, что компилятору это не выгодно, но временные файлы легче отлаживать, чем встроенные.
2. @TomKerr это только из-за отладчика. 🙁
3. @R.MartinhoFernandes Уверяю вас, что это чувство не утешает меня, когда мне приходится отлаживать чужой код, совсем наоборот. Это тоже непростая проблема с интерфейсом, я чувствую их боль.
Ответ №3:
Я не думаю, что const amp;
привязка к временному для продления срока службы настолько распространена. На самом деле, в C 03 во многих случаях копия может быть удалена, просто передав значение и вызвав функцию в том, что вы называете встроенной формой: some_func( int_func(), obj_func() )
, поэтому та же проблема, которую вы заметили в C 11, возникнет в C 03 (в несколько ином видеспособ)
Что касается привязки ссылки const, в случае, obj_func()
если возвращается объект типа T
, приведенный выше код — это просто громоздкий способ выполнения T obj = obj_func();
, который не дает никаких преимуществ, кроме как заставлять людей задаваться вопросом, зачем это было нужно.
Если obj_func()
возвращает тип T1
, производный от T
, этот трюк позволяет игнорировать точный возвращаемый тип, но это также может быть достигнуто с помощью auto
ключевого слова, так что в любом случае у вас есть локальная именованная переменная obj
.
Правильный способ передать obj
в функцию — если вы закончили с этим, и функция может перемещать значение из obj
во внутренний объект, — это фактически переместить:
some_func( a, std::move(obj) );