Const ссылается на временную оптимизацию возвращаемого значения - программирование
Подтвердить что ты не робот

Const ссылается на временную оптимизацию возвращаемого значения

Мне известно о том, что назначение rvalue для ссылки на константу lvalue продлевает время жизни во времени до конца области. Однако мне непонятно, когда использовать это и когда полагаться на оптимизацию возвращаемого значения.

LargeObject lofactory( ... ) {
     // construct a LargeObject in a way that is OK for RVO/NRVO
}

int main() {
    const LargeObject& mylo1 = lofactory( ... ); // using const&
    LargeObject mylo2 = lofactory( ... ); // same as above because of RVO/NRVO ?
}

Согласно "Более эффективному С++" Скот Майерс (пункт 20), второй метод может быть оптимизирован компилятором для создания объекта на месте (что было бы идеально и точно, чего можно достичь с помощью const& в первом метод).

  • Есть ли общепринятые правила или рекомендации, когда использовать const& для временных и когда полагаться на RVO/NRVO?
  • Может ли быть ситуация, когда использование метода const& хуже, чем его использование? (Я думаю, например, о семантике перемещения С++ 11, если LargeObject реализовано...)
4b9b3361

Ответ 1

Рассмотрим наиболее простой случай:

lofactory( ... ).some_method();

В этом случае возможно копирование из lofactory в контекст вызывающего абонента – но его можно оптимизировать с помощью RVO/NRVO.


LargeObject mylo2 ( lofactory( ... ) );

В этом случае возможны следующие копии:

  • Возвращает временный из lofactory в контекст вызывающего – можно оптимизировать с помощью RVO/NRVO
  • Копировать-конструкцию mylo2 из временного – можно оптимизировать копирование

const LargeObject& mylo1 = lofactory( ... );

В этом случае возможна еще одна копия:

  • Возвращает временный из lofactory в контекст вызывающего – можно оптимизировать с помощью RVO/NRVO (тоже!)

Ссылка будет привязана к этому временному.


Итак,

Существуют ли общепринятые правила или рекомендации, когда использовать const & к временным и когда полагаться на RVO/NRVO?

Как я уже говорил выше, даже в случае с const& возможна ненужная копия, и ее можно оптимизировать с помощью RVO/NRVO.

Если в некоторых случаях ваш компилятор применяет RVO/NVRO, то, скорее всего, он выполнит копирование на этапе 2 (см. выше). Потому что в этом случае копирование намного проще, чем NRVO.

Но в худшем случае у вас будет одна копия для случая const& и две копии при инициализации значения.

Может ли быть ситуация, при которой использование const & метод хуже, чем не использовать его?

Я не думаю, что есть такие случаи. По крайней мере, если ваш компилятор не использует странные правила, которые различают const&. (Пример аналогичной ситуации я заметил, что MSVC не выполняет NVRO для агрегатной инициализации.)

(Я думаю, например, о семантике перемещения С++ 11, если у LargeObject есть те, которые были реализованы...)

В С++ 11, если LargeObject имеет семантику перемещения, то в худшем случае вы будете иметь один ход для случая const& и два шага при инициализации значения. Итак, const& все еще немного лучше.


Таким образом, хорошим правилом было бы всегда привязывать временные константы к const & если это возможно, так как это может помешать копированию, если компилятор почему-то не смог сделать копию-исключение?

Без знания фактического контекста приложения это кажется хорошим правилом.

В С++ 11 можно привязать временную ссылку на rvalue - LargeObject & &. Таким образом, такое временное может быть изменено.


Кстати, перемещение семантической эмуляции доступно для С++ 98/03 с помощью разных трюков. Например:

Однако даже при наличии семантики перемещения - есть объекты, которые не могут быть дешево перемещены. Например, матричный класс 4x4 с двойными данными [4] [4] внутри. Итак, Copy-elision RVO/NRVO по-прежнему очень важны, даже в С++ 11. И, кстати, когда происходит копирование /RVO/NRVO - это быстрее, чем перемещение.


P.S., в реальных случаях есть несколько дополнительных вещей, которые следует учитывать:

Например, если у вас есть функция, которая возвращает вектор, даже если Move/RVO/NRVO/Copy-Elision будет применена - она ​​все равно может быть не эффективной на 100%. Например, рассмотрим следующий случай:

while(/*...*/)
{
    vector<some> v = produce_next(/* ... */); // Move/RVO/NRVO are applied
    // ...
}

Эффективнее будет изменить код на:

vector<some> v;
while(/*...*/)
{
    v.clear();

    produce_next( v ); // fill v
    // or something like:
    produce_next( back_inserter(v) );
    // ...
}

Поскольку в этом случае уже выделенный внутренний вектор памяти может быть повторно использован, когда v.capacity() достаточно, без необходимости делать новые выделения внутри product_next на каждой итерации.

Ответ 2

Если вы напишете свой класс lofactory следующим образом:

LargeObject lofactory( ... ) {
    // figure out constructor arguments to build a large object
    return { arg1, arg2, arg3 }  //  return statement with a braced-init-list
}

В этом случае нет RVO/NRVO, это прямая конструкция. В разделе 6.6.3 стандарта говорится, что оператор A return с бин-init-списком инициализирует объект или ссылку, которые должны быть возвращены из функции путем копирования-списка-инициализации (8.5.4) из указанного списка инициализаторов. "

Затем, если вы захватите свой объект с помощью

LargeObject&& mylo = lofactory( ... ); 

не будет никакого копирования, срок жизни будет тем, что вы ожидаете, и вы можете изменить mylo.

И все без копирования в любом месте гарантировано.