При броске предмета он копируется или перемещается? - PullRequest
2 голосов
/ 11 марта 2020

Насколько я знаю, брошенные объекты копируются по умолчанию. Поэтому конструктор копирования должен вызываться, когда я бросаю объект. Я также знаю, что компилятор может оптимизировать и исключить копирование. У меня есть простая программа, в которой я создаю класс с именем X. Ожидается, что если я сделаю класс не подлежащим копированию, объект не может быть брошен. Но происходит нечто неожиданное. Если я удаляю конструктор копирования, компилятор жалуется на это. Если я прокомментирую удаление конструктора копирования и удаляю конструктор перемещения, компилятор жалуется на удаление конструктора перемещения. код:

class X
{
public:
    int code;
    X(int code) : code(code) {}
    //X(X&) = delete;
    //X(X&&) = delete;
};

void func()
{
    X x(4);
    throw x;
}

int main() { }

Ошибка при удалении конструктора копирования:

main.cpp: In function ‘void func()’:
main.cpp:13:11: error: use of deleted function ‘X::X(X&)’
     throw x;
           ^

Ошибка при удалении конструктора перемещения:

main.cpp: In function ‘void func()’:
main.cpp:13:11: error: use of deleted function ‘X::X(X&&)’
     throw x;
           ^

Может кто-нибудь объяснить, почему это происходит?

Редактировать: Если я удаляю конструктор копирования и предоставляю реализацию для конструктора перемещения, код работает нормально. Принимая во внимание, что если я удаляю конструктор перемещения и предоставляю реализацию для конструктора копирования, я все равно получаю ту же ошибку. Почему я не могу удалить конструктор перемещения?

код:

class X
{
public:
    int code;
    X(int code) : code(code) {}
    X(X&) = default;
    X(X&&) = delete;
};

void func()
{
    X x(5);
    throw x;
}

int main() { }

ошибка:

main.cpp: In function ‘void func()’:
main.cpp:13:11: error: use of deleted function ‘X::X(X&&)’
     throw x;
           ^

Ответы [ 3 ]

2 голосов
/ 11 марта 2020

Инициализация объекта исключения следует тем же правилам, что и инициализация других объектов: когда инициализатором является lvalue, используется конструктор копирования, а когда инициализатором является rvalue, используется конструктор перемещения. Однако см. C ++ 17 [class.copy.elision] / 3:

... если операндом throw-expression (8.17) является имя энергонезависимый автоматический c объект (отличный от параметра функции или оператора catch), область которого не выходит за пределы самого внутреннего вмещающего try-block (если он есть), разрешение перегрузки Выбор конструктора для копии сначала выполняется так, как если бы объект был обозначен значением r. Если первое разрешение перегрузки не удалось или не было выполнено, или если тип первого параметра выбранного конструктора не является rvalue-ссылкой на тип объекта (возможно, cv-квалифицированный), разрешение перегрузки выполняется снова, рассматривая объект как именующий. [ Примечание: Это двухэтапное разрешение перегрузки должно выполняться независимо от того, будет ли выполнено копирование. Он определяет конструктор, который будет вызван, если elision не выполняется, и выбранный конструктор должен быть доступен, даже если вызов исключен. - конец примечания ]

Здесь вы бросаете объект в контекст, где он сначала рассматривается как значение. Если не удается разрешить перегрузку, оно будет рассматриваться как lvalue. Поэтому в общем случае компилятор предпочитает использовать конструктор перемещения.

  1. Когда конструктор копирования явно удаляется, компилятор не создает конструктор перемещения ([class.copy.ctor] / 8), поэтому код не компилируется.

  2. Когда конструктор перемещения явно удаляется, на первом этапе разрешения перегрузки выбирается конструктор перемещения. Произошла ошибка, поскольку функция, выбранная с помощью разрешения перегрузки, удалена. (Примечание: конструктор перемещения по умолчанию, который определен как удаленный, игнорируется разрешением перегрузки ([class.copy.ctor] / 10). Явно удаленный конструктор перемещения не игнорируется разрешением перегрузки.)

  3. Когда конструкторы копирования и перемещения явно удалены, предыдущий абзац остается в силе.

1 голос
/ 11 марта 2020

Цитирование из cppreference

Даже если инициализация копирования выбирает конструктор перемещения, инициализация копирования из lvalue должна быть правильной, а деструктор должен быть доступен (так как C + +14)

И

Если тип expression является типом класса, его конструктор копирования / перемещения и деструктор должны быть доступны, даже если имеет место удаление копии ,

Очевидно, для четко определенного класса исключений необходим конструктор копирования, даже если он не будет использоваться. Стандарт C ++ 11 не предписывал это , но это было исправлено в C ++ 14.

Я не уверен на 100%, как интерпретировать «. Это может вызвать конструктор перемещения для выражения rvalue » - нужен ли конструктор перемещения или нет. Для обратной совместимости этого не должно быть, но, возможно, кто-то может процитировать стандарт на это.

1 голос
/ 11 марта 2020

При броске объекта он копируется или перемещается?

Брошенный объект будет инициализирован копированием, а локальный объект lvalue будет рассматриваться как значение r - как если бы возвращая локальное lvalue.

Таким образом, если у типа есть конструктор перемещения, он будет перемещен в вашем примере. Если класс только копируемый, но не подвижный, то он будет скопирован. Аналогично, если выражение броска не является (рассматривается как) значением r, оно копируется.

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

...