Именованные объекты и временные объекты: лучше ли избегать названных объектов, когда это возможно? - программирование
Подтвердить что ты не робот

Именованные объекты и временные объекты: лучше ли избегать названных объектов, когда это возможно?

Ниже приведен фрагмент, который я нашел в документации по стилю кодирования для библиотеки:

По возможности, лучше использовать временную, а не сохранение именованного объекта, например:

DoSomething (XName ( "blah" ));

а не

XName n ( "blah" ); DoSomething (n);

поскольку это облегчает компилятору оптимизацию вызова, может уменьшить размер стека функции и т.д. Не забывайте учитывать время жизни временного, однако.

Предполагая, что объект не нуждается в изменении, а проблемы с жизненным циклом не являются проблемой, верно ли это правило? Я думал, что в наши дни это не изменит ситуацию. Однако в некоторых случаях вы не могли избежать именованного объекта:

XName n("blah");
// Do other stuff to mutate n
DoSomething( n );

Кроме того, с семантикой перемещения мы можем написать такой код, поскольку временные исключения устраняются:

std::string s1 = ...;
std::string s2 = ...;
std::string s3 = ...;
DoSomething( s1 + s2 + s3 );

а не (я слышал, что компилятор может лучше оптимизировать со следующим в С++ 03):

std::string s1 = ...;
std::string s2 = ...;
std::string s3 = ...;
s1 += s2; 
s1 += s3;  // Instead of s1 + s2 + s3
DoSomething(s1);

(Конечно, приведенное выше может сводиться к measure and see for yourself, но мне было интересно, имеет ли вообще общее упоминание выше).

4b9b3361

Ответ 1

Основная задача интерфейса компилятора - удалить имена из всего, чтобы разрешить основные семантические структуры.

Стремясь избежать имен, помогает избежать ненужного обращения к адресам объектов, что может неинтуитивно остановить компилятор от манипулирования данными. Но есть достаточно способов получить адрес временного, что все это, но спорный. И названные объекты особенны в том, что они не имеют права на конструкторский эликсир в С++, но, как вы говорите, перемещение семантики устраняет наиболее дорогостоящую ненужную копию-конструкцию.

Просто сосредоточьтесь на написании читаемого кода.

Ваш первый пример устраняет копию n, но в С++ 11 вместо этого вы можете использовать семантику перемещения: DoSomething( std::move( n ) ).

В примере s1 + s2 + s3 также верно, что С++ 11 делает вещи более эффективными, но перемещение семантики и устранение временных ситуаций - это разные вещи. Конструктор перемещения просто делает строительство временного менее дорогостоящим.

Я тоже был под недоразумением, что С++ 11 устранит временные файлы, если вы использовали идиому

// What you should use in C++03
foo operator + ( foo lhs, foo const & rhs ) { return lhs += rhs; }

Это фактически неверно; lhs - это именованный объект, а не временный, и он не подходит для формы оптимизации возвращаемого значения. На самом деле, в С++ 11 это создаст копию, а не движение! Вам нужно исправить это с помощью std::move( lhs += rhs );.

// What you should use in C++11
foo operator + ( foo lhs, foo const & rhs ) { return std::move( lhs += rhs ); }

В вашем примере используется std::string, а не foo, и что operator+ определяется (по существу, и с С++ 03) как

// What the C++03 Standard Library uses
string operator + ( string const & lhs, string const & rhs )
    { return string( lhs ) += rhs; } // Returns rvalue expression, as if moved.

Эта стратегия имеет сходные свойства с вышеизложенным, поскольку временное дисквалифицировано для копирования, когда оно привязано к ссылке. Есть два возможных исправления, которые дают выбор между скоростью и безопасностью. Ни одно исправление не совместимо с первой идиомой, которая с move уже реализует безопасный стиль (и как таковой вы должны использовать!).

Безопасный стиль.

Здесь нет именованных объектов, но временная привязка к аргументу lhs не может быть напрямую сконструирована в привязке результата к эталонному прекращению копирования.

// What the C++11 Standard Library uses (in addition to the C++03 library style)
foo operator + ( foo && lhs, foo const & rhs )
    { return std::move( lhs += rhs ); }

Небезопасный стиль.

Вторая перегрузка, принимающая ссылку rvalue и возвращающая ту же ссылку, полностью исключает промежуточное временное (не полагается на elision), позволяя цепочке вызовов + идеально преобразовываться в вызовы +=. Но, к сожалению, он также дисквалифицирует оставшиеся временные в начале цепочки вызовов из продления жизни, привязывая его к ссылке. Таким образом, возвращаемая ссылка действительна до точки с запятой, но затем она уходит, и ничто не может ее остановить. Таким образом, это в основном полезно внутри чего-то вроде библиотеки выражений шаблонов с документированными ограничениями на то, какие результаты могут быть привязаны к локальной ссылке.

// No temporary, but don't bind this result to a local!
foo && operator + ( foo && lhs, foo const & rhs )
    { return std::move( lhs += rhs ); }

Оценка документации библиотеки как таковой требует небольшой оценки навыков авторов библиотеки. Если они говорят, что делают что-то необычно, потому что это всегда более эффективно, будьте скептически настроены, потому что С++ не предназначен специально для того, чтобы быть изворотливым, но он призван быть эффективным.

Однако в случае шаблонов выражения, где временные числа включают в себя сложные вычисления типа, которые будут прерваны присвоением именованной переменной конкретного типа, вы должны абсолютно слушать то, что говорят авторы. В таком случае они были бы, вероятно, гораздо более осведомленными.

Ответ 2

Я думаю, что принятый ответ неправильный. Лучше избегать присвоения имен временным объектам.

Причина в том, что если у вас

struct T { ... };
T foo(T obj) { return obj; }

// ...

T t;
foo(t);

тогда t будет построено по копиям, а не может быть оптимизировано, если конструктор-копир имеет наблюдаемые побочные эффекты.

В отличие от этого, если вы сказали foo(T()), то вызов конструктора-копии можно было бы полностью исключить, независимо от потенциальных побочных эффектов.

Таким образом, избегая присвоения имен временным объектам, лучше всего практиковать.

Ответ 3

Вот несколько моментов:

  • Вы не можете точно знать, какой компилятор будет оптимизировать, а что - нет. Оптимизация - сложная вещь. Авторы оптимизаторов, как правило, очень осторожны, чтобы не сломать что-то. Можно исправить ошибку, которую оптимизатор ошибочно решает, что что-то не следует оптимизировать. Стандарты кодирования в компиляторах чрезвычайно высоки. Тем не менее они написаны людьми.
  • Этот фрагмент стиля кодирования не кажется мне очень разумным. Наши составители дней почти всегда хороши. Трудно представить, что оптимизатор смутит что-то в XName n("blah"); DoSomething(n); - этот код слишком прост.

Я бы поставил аналогичное руководство по кодированию следующим образом:

  • Напишите свой код таким образом, который легко понять и изменить;
  • Как только проблемы с производительностью наблюдаются, загляните в сгенерированный код и подумайте как угодить компилятору.

Лучше решить проблему в этом порядке, а не наоборот.