Копирование исключения и ссылки на rvalue

#c #c 11

#c #c 11

Вопрос:

что используется в приведенном ниже коде, чтобы избежать копирования, исключения или ссылки на rvalue и переместить конструктор ?

 std::string get(){return "...";}

void foo(std::string var){}

foo( get() ); //<--- here
  

Ответ №1:

 std::string get(){
    // this is similar to return std::string("..."), which is
    // copied/moved into the return value object.
    return "...";
}
  

RVO позволяет ему создавать временный объект string непосредственно в объект возвращаемого значения get() .

 foo( get() );
  

RVO позволяет ему напрямую создавать временный строковый объект (объект возвращаемого значения) непосредственно в объект параметра foo .

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

В других случаях компилятор должен рассматривать объекты как значения rvalues, даже если в противном случае они являются значениями lvalues

 std::string get(){
    std::string s = "...";
    // this is similar to return move(s)
    return s;
}
  

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

Пример:

 struct A { A(); A(Aamp;); };
struct B { B(); B(Bamp;amp;); };

A f() { A a; return a; }
B f() { B b; return b; }
  

Для первого он принимает a как rvalue, но не может найти конструкторы, которые принимают это rvalue ( Aamp; не может привязываться к rvalues). Поэтому затем он снова обрабатывает a как то, что оно есть (значение lvalue). Во втором случае он принимает b в качестве rvalue и должен B(Bamp;amp;) принять это rvalue и переместить его. Если бы оно было принято b в качестве значения lvalue (что это такое), то инициализация копирования завершилась бы неудачей, потому что B неявно объявлен конструктор копирования.


Обратите внимание, что при возврате и передаче параметров используются правила инициализации копирования, что означает

 u -> T (where u's type is different from T) =>
  T rvalue_tmp = u;
  T target(rvalue_tmp);

t -> T (where t's type is T) =>
  T target = t;
  

Следовательно, в примере, где мы возвращаем "..." , мы сначала создаем временное значение rvalue, а затем перемещаем его в целевой объект. В случае, когда мы возвращаем выражение типа возвращаемого значения / параметра, мы непосредственно переместим / скопируем выражение в целевой объект.

Ответ №2:

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

Ответ №3:

Реализация определена, но, скорее всего, исключение копирования.

Аналогично, RVO / NRVO, скорее всего, сработает перед перемещением семантики при возврате значения объекта из функции.