Возвращаемое значение C ++ и исключения правил перемещения - PullRequest
0 голосов
/ 30 мая 2020

Когда мы возвращаем значение из функции C ++, происходит копирование-инициализация. Например:

std::string hello() {
    std::string x = "Hello world";
    return x; // copy-init
}

Предположим, что RVO отключен.

Согласно правилу copy-init, если x не является типом класса POD, то копия конструктор должен быть вызван. Однако для C ++ 11 и далее я вижу, что вызывается конструктор перемещения. Я не смог найти или понять правила относительно этого https://en.cppreference.com/w/cpp/language/copy_initialization. Итак, мой первый вопрос -

  1. Что стандарт C ++ говорит о перемещении, происходящем для copy-init, когда значение возвращается из функции?

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

std::string hello2(std::string& param) {
    return param;
}

Наконец, в некотором коде библиотеки я увидел, что std::move явно используется при возврате (даже если RVO или ход должен произойти). Например:

std::string hello3() {
    std::string x = "Hello world";
    return std::move(x);
}
В чем преимущество и недостаток явного использования std::move при возврате?

1 Ответ

1 голос
/ 30 мая 2020

Вы сбиты с толку тем фактом, что инициализация с помощью конструктора перемещения является частным случаем «инициализации копии» и не является отдельной концепцией. Проверьте примечания на странице cppreference.

Если other является выражением rvalue, конструктор перемещения будет выбран при разрешении перегрузки и вызван во время инициализации копирования. Нет такого термина, как инициализация перемещения.

Для возврата значения из функции проверьте описание возврата по cppreference . В поле указано «автоматическое c перемещение от локальных переменных и параметров», где выражение относится к тому, что вы возвращаете (предупреждение: эта цитата сокращена! Полные сведения о других случаях см. В оригинале) :

Если выражение - это id-выражение (возможно, заключенное в скобки), которое именует переменную, тип которой [...] является энергонезависимым типом объекта [...] и эта переменная объявляется [...] в теле или как параметр функции [...], затем разрешение перегрузки для выбора конструктора, используемого для инициализации возвращаемого значения, выполняется дважды: сначала, как если бы выражение было выражение rvalue (таким образом, оно может выбрать конструктор перемещения), и если первое разрешение перегрузки не удалось [...], то разрешение перегрузки выполняется как обычно, с выражением, рассматриваемым как lvalue (так что оно может выбрать конструктор копирования).

Таким образом, в особом случае возврата локальной переменной переменную можно рассматривать как r-значение, даже если нормальное s Правила yntacti c сделают его l-значением. Суть правила заключается в том, что после возврата вы не можете узнать, было ли значение локальной переменной уничтожено во время копирования-инициализации возвращенного значения, поэтому его перемещение не причиняет никакого вреда.

Относительно вашего второго вопроса: Считается плохим стилем использовать std::move при возврате , потому что перемещение все равно произойдет, и это запрещает NRVO .

Цитата из основных руководящих принципов C ++, указанных выше:

Никогда не пишите return move(local_variable);, потому что язык уже знает, что переменная является кандидатом на перемещение. Запись move в этом коде не поможет и может нанести вред, поскольку на некоторых компиляторах это мешает RVO (оптимизации возвращаемого значения), создавая дополнительный псевдоним ссылки на локальную переменную.

Таким образом, код библиотеки, который вы цитируете, является неоптимальным. все еще виден после возврата функции. В цитате из cppreference важным моментом является «энергонезависимый объект тип». Когда вы возвращаете std::string& param, это переменная с типом ссылка .

...