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

Мне известен тот факт, что присвоение rvalue для ссылки на const 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 ?
}

Согласно Scot Meyers ‘More Effective C ++ (пункт 20), компилятор может оптимизировать второй метод для создания объекта на месте (который будет идеальным и именно того, чего вы пытаетесь достичь с помощью const& в первом методе).

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

17

Решение

Давайте рассмотрим самый простой случай:

lofactory( ... ).some_method();

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


LargeObject mylo2 ( lofactory( ... ) );

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

  1. Вернуть временный от lofactory в контекст вызывающего абонента — можно оптимизировать РВО / NRVO
  2. Copy-конструкт mylo2 от временный — может быть оптимизирован копирования элизия

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

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

  1. Вернуть временный от lofactory в контекст вызывающего абонента — можно оптимизировать РВО / NRVO (тоже!)

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


Так,

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

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

Если ваш компилятор применяется РВО / NVRO в некоторых случаях, скорее всего, он будет выполнять копирование на этапе 2 (выше). Потому что в этом случае copy-elision намного проще, чем NRVO.

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

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

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

(Я думаю, например, о семантике перемещения в C ++ 11, если в LargeObject реализована такая …)

В C ++ 11, если LargeObject имеет семантику ходов, то в худшем случае у вас будет один ход для const& случай, и два шага, когда вы начинаете значение. Так, const& все еще немного лучше.


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

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

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


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

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


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

Например, если у вас есть функция, которая возвращает вектор, даже если будет применяться 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 () достаточно, без необходимости делать новые выделения внутри yield_next на каждой итерации.

13

Другие решения

Если вы напишите свой 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 стандарта гласит: «А return заявление с приготовился-INIT-лист инициализирует объект или ссылку, которые будут возвращены из функции путем копирования-инициализации списка (8.5.4) из указанного списка инициализатора ».

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

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

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

И все без копирования нигде, гарантировано.

7

По вопросам рекламы [email protected]