Как работает гарантия elision?

На совещании по стандартам Oulu ISO C++ 2016 года комитет по стандартам проголосовал за предложение под названием " Гарантированное исключение копирования через упрощенные категории значений".

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

2 ответа

Решение

Разрешение на копирование было разрешено при нескольких обстоятельствах. Однако, даже если это было разрешено, код все равно должен был работать так, как если бы копия не была удалена. А именно, должен был быть доступный конструктор копирования и / или перемещения.

Гарантированное удаление копий переопределяет ряд концепций C++, так что определенные обстоятельства, при которых могут быть исключены копии / перемещения, на самом деле вообще не провоцируют копирование / перемещение. Компилятор не исключает копию; Стандарт гласит, что такого копирования не может быть.

Рассмотрим эту функцию:

T Func() {return T();}

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

Так же:

T t = Func();

Это копия инициализации t, Это будет копировать инициализировать t с возвращаемым значением Func, Тем не мение, T все еще должен иметь конструктор перемещения, даже если он не будет вызван.

Гарантированная копия elision переопределяет значение выражения prvalue. До C++17 prvalues ​​являются временными объектами. В C++17 выражение prvalue - это просто нечто, что может материализоваться как временное, но пока оно не является временным.

Если вы используете prvalue для инициализации объекта типа prvalue, то временное не материализуется. Когда вы делаете return T();, это инициализирует возвращаемое значение функции через prvalue. Поскольку эта функция возвращает T временное не создается; инициализация prvalue просто напрямую инициализирует возвращаемое значение.

Следует понимать, что, поскольку возвращаемое значение является prvalue, оно еще не является объектом. Это просто инициализатор для объекта, так же, как T() является.

Когда вы делаете T t = Func();, значение возвращаемого значения напрямую инициализирует объект t; нет этапа "создать временный и скопировать / переместить". поскольку Func() возвращаемое значение является предварительным значением, эквивалентным T(), t напрямую инициализируется T() точно так же, как если бы вы сделали T t = T(),

Если prvalue используется любым другим способом, prvalue материализует временный объект, который будет использоваться в этом выражении (или удаляется, если выражения нет). Так что если вы сделали const T &rt = Func();, prvalue материализуется временный (используя T() в качестве инициализатора), чья ссылка будет храниться в rt наряду с обычным временным продлением жизни.

Единственное, что гарантированно разрешает elision, - это возвращать объекты, которые неподвижны. Например, lock_guard не может быть скопирован или перемещен, поэтому у вас не может быть функции, которая возвращает его по значению. Но с гарантированным разрешением копирования вы можете.

Гарантированное разрешение также работает с прямой инициализацией:

new T(FactoryFunction());

Если FactoryFunction возвращается T по значению это выражение не будет копировать возвращаемое значение в выделенную память. Вместо этого он будет выделять память и использовать выделенную память в качестве памяти возвращаемого значения для вызова функции напрямую.

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

Конечно, это тоже работает:

new auto(FactoryFunction());

Если вам не нравится писать имена.


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

T Func()
{
   T t = ...;
   ...
   return t;
}

В этом случае, t должен иметь доступный конструктор копирования / перемещения. Да, компилятор может оптимизировать копирование / перемещение. Но компилятор все равно должен проверить существование доступного конструктора копирования / перемещения.

Так что ничего не меняется для именованной оптимизации возвращаемого значения (NRVO).

Я думаю, что здесь подробно рассказали о копировании. Однако я нашел эту статью: https://jonasdevlieghere.com/guaranteed-copy-elision, которая относится к гарантированному исключению копирования в C++17 в случае оптимизации возвращаемого значения.

Это также относится к тому, как, используя параметр gcc: -fno-elide-constructors, можно отключить исключение копирования и увидеть, что вместо конструктора, вызываемого напрямую в месте назначения, мы видим 2 конструктора копирования (или конструкторы перемещения в С ++11) и вызываемые им соответствующие деструкторы. В следующем примере показаны оба случая:

      #include <iostream>
using namespace std;
class Foo {
public:
    Foo() {cout << "Foo constructed" << endl; }
    Foo(const Foo& foo) {cout << "Foo copy constructed" << endl;}
    Foo(const Foo&& foo) {cout << "Foo move constructed" << endl;}
    ~Foo() {cout << "Foo destructed" << endl;}
};

Foo fReturnValueOptimization() {
    cout << "Running: fReturnValueOptimization" << endl;
    return Foo();
}

Foo fNamedReturnValueOptimization() {
    cout << "Running: fNamedReturnValueOptimization" << endl;
    Foo foo;
    return foo;
}

int main() {
    Foo foo1 = fReturnValueOptimization();
    Foo foo2 = fNamedReturnValueOptimization();
}
vinegupt@bhoscl88-04(~/progs/cc/src)$ g++ -std=c++11 testFooCopyElision.cxx # Copy elision enabled by default
vinegupt@bhoscl88-04(~/progs/cc/src)$ ./a.out
Running: fReturnValueOptimization
Foo constructed
Running: fNamedReturnValueOptimization
Foo constructed
Foo destructed
Foo destructed
vinegupt@bhoscl88-04(~/progs/cc/src)$ g++ -std=c++11 -fno-elide-constructors testFooCopyElision.cxx # Copy elision disabled
vinegupt@bhoscl88-04(~/progs/cc/src)$ ./a.out
Running: fReturnValueOptimization
Foo constructed
Foo move constructed
Foo destructed
Foo move constructed
Foo destructed
Running: fNamedReturnValueOptimization
Foo constructed
Foo move constructed
Foo destructed
Foo move constructed
Foo destructed
Foo destructed
Foo destructed

Я вижу, что оптимизация возвращаемого значения. Т.е. копирование временных объектов в операторах возврата обычно гарантируется независимо от С ++ 17.

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

Было бы неплохо, если бы был способ гарантировать исключение копирования или получить какое-то предупреждение, когда удаление копирования не может быть выполнено, что заставило бы разработчиков убедиться, что выполняется исключение копирования, и повторно факторировать код, если его невозможно выполнить .

Другие вопросы по тегам