Гарантированное удаление копии и удаление конструктора копирования/перемещения при возникновении исключения

avatar
Pluto
9 августа 2021 в 03:38
146
2
2

Начиная с C++17 значение prvalue изменилось, что в некоторых случаях гарантирует исключение копирования. Из cppreference конструкторы копирования/перемещения не обязательно должны присутствовать или быть доступными в этом случае.

При возникновении исключения объект исключения инициализируется копированием, и при копировании/перемещении может возникнуть ошибка копирования. Но требуется ли, чтобы в это время был доступен конструктор копирования/перемещения?

От [кроме.throw]:

Когда брошенный объект является объектом класса, конструктор, выбранный для инициализации копирования, а также конструктор, выбранный для инициализации копирования, рассматривающий брошенный объект как lvalue, должен быть неудаляемым и доступным, даже если операция копирования/перемещения опущена ([class.copy.elision]). Деструктор потенциально вызывается ([class.dtor]).

В стандарте упоминается, что соответствующий конструктор не должен удаляться и быть доступным, даже если он опущен.

Тем не менее, я протестировал и обнаружил, что и GCC, и Clang позволяют создавать исключения при удалении соответствующего конструктора:

struct A {
    A() = default;
    A(A&&) = delete;
};

try {
    throw A{};    // copy elision
} catch(...) {}

Код компилируется, но не соответствует требованиям стандарта. Если я уменьшу версию с C++17 до C++14, они оба сообщат об ошибках:

struct A {
    A() = default;
    A(A&&) = delete;
};

try {
    throw A{};    // error: call to deleted constructor of 'A'
} catch(...) {}

Я неправильно понимаю стандарт или компилятор просто ослабляет ограничения?

Источник

Ответы (2)

avatar
Nicol Bolas
9 августа 2021 в 03:57
0

Ваше понимание стандарта верно, поскольку ваш анализ этого абзаца действительно применим.

Но это не применимо, потому что ни один конструктор никогда не рассматривался для инициализации копированием.

Гарантированное исключение — это неправильное название; это полезное объяснение концепции, но оно не отражает точно, как это работает в отношении стандарта. Гарантированное исключение работает, переписывая значение prvalue, поэтому в первую очередь никогда не бывает копии/перемещения, которое нужно исключить.

A a = A{}; — это инициализация копированием, но он даже гипотетически не вызывает конструктор копирования/перемещения. Переменная a инициализирована инициализатором prvalue:

Результатом prvalue является значение, которое выражение сохраняет в своем контексте. Иногда говорят, что значение prvalue, результатом которого является значение V, имеет значение V или называет его.

a — это «результирующий объект», инициализированный значением prvalue.

То же самое и здесь. A{} — это значение. Объект исключения — это «результирующий объект», который должен быть инициализирован значением prvalue. Нет ни временных объектов, ни конструкторов копирования/перемещения, которые когда-либо рассматривались для использования.

Pluto
9 августа 2021 в 07:37
1

Спасибо. Я понял ваш ответ. Итак, выбранный конструктор в этом примере является просто конструктором по умолчанию? Кроме того, можете ли вы объяснить или привести пример о том, что «конструктор, выбранный для инициализации копирования, рассматривает брошенный объект как lvalue»?

avatar
Arthur P. Golubev
26 августа 2021 в 23:11
0

Во-первых, C++17 гарантирует удаление копии ни при создании объекта исключения, ни при активации обработчика исключений.

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

.

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