Подтвердить что ты не робот

Почему RVO запрещен при возврате параметра?

Это указано в [С++ 11:12.8/31]:

Это разрешение операций копирования/перемещения, называемое копированием, разрешено [...]:

- в операторе return в функции с возвращаемым типом класса, когда выражение является именем энергонезависимого автоматического объекта (, кроме функции или параметра catch-clause), с такой же cv-неквалифицированный тип, как тип возвращаемого значения, операцию копирования/перемещения можно опустить, построив автоматический объект непосредственно в возвращаемое значение функции

Это означает, что

#include <iostream>

using namespace std;

struct X
{
    X() { }
    X(const X& other) { cout << "X(const X& other)" << endl; }
};

X no_rvo(X x) {
    cout << "no_rvo" << endl;
    return x;
}

int main() {
    X x_orig;
    X x_copy = no_rvo(x_orig);

    return 0;
}

напечатает

X(const X& other)
no_rvo
X(const X& other)

Почему требуется второй конструктор копирования? Не может ли компилятор просто продлить время жизни x?

4b9b3361

Ответ 1

Представьте, что no_rvo определяется в другом файле, чем main, поэтому при компиляции main компилятор увидит только объявление

X no_rvo(X x);

и не будет знать, имеет ли объект типа X какое-либо отношение к аргументу. Из того, что он знает в этот момент, реализация no_rvo также может быть

X no_rvo(X x) { X other; return other; }

Итак, когда это, например, компилирует строку

X const& x = no_rvo(X());

он будет делать следующее, когда максимально оптимизируется.

  • Сгенерировать временный X, который будет передан no_rvo в качестве аргумента
  • вызовите no_rvo и привяжите его возвращаемое значение к X
  • уничтожить временный объект, который он передал no_rvo.

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

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

Ответ 2

Обычная реализация RVO заключается в том, что вызывающий код передает адрес блока памяти, где функция должна строить свой объект result.

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

Для аргумента, переданного по значению, код вызывающей машины должен копировать-инициализировать свой фактический аргумент в формальное расположение аргумента & rsquo; s перед тем, как перейти к функции. Для того чтобы функция могла поместить свой результат там, сначала пришлось бы уничтожить формальный объект аргумента, который имеет некоторые сложные специальные случаи (например, когда эта конструкция прямо или косвенно относится к формальному объекту аргумента). Таким образом, вместо определения местоположения результата с формальным местоположением аргумента, здесь логическая логика должна использовать отдельный вызываемый блок памяти для результата функции.

Однако результат функции, который не передается в регистр, обычно предоставляется вызывающим. То есть, о чем можно было бы рассуждать в качестве RVO, своего рода уменьшенном RVO, для случая выражения return, которое обозначает формальный аргумент, все равно произойдет. И это не соответствует тексту "путем создания автоматического объекта непосредственно в возвращаемое значение функций".

Подводя итог, поток данных, требующий, чтобы вызывающий объект передавал значение, означает, что он обязательно является вызывающим, который инициализирует хранилище формальных аргументов, а не функцию. Следовательно, нельзя вообще отказаться от формального аргумента (этот термин ласки охватывает особые случаи, когда компилятор может делать очень специальные вещи, в частности, для встроенного машинного кода). Тем не менее, это функция, которая инициализирует любое другое локальное автоматическое хранилище объектов, а затем не работает RVO.