Почему RVO запрещен при возврате параметра? - PullRequest
21 голосов
/ 25 февраля 2012

В [C ++ 11: 12.8 / 31] указано:

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

- в операторе возврата в функции с типом возврата класса, когда выражением является имя энергонезависимого автоматического объекта (, отличного от параметра функции или оператора catch * ) с тем же 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 ?

Ответы [ 2 ]

11 голосов
/ 25 февраля 2012

Представьте, 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 было альтернативной реализацией, которую я показал выше. Поэтому, если функции разрешено повторно использовать аргумент в качестве возвращаемого значения, могут возникнуть ситуации, когда компилятор не сможет определить правильное поведение.

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

5 голосов
/ 25 февраля 2012

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...