Зачем нам нужно использовать виртуальную ~ A() = default; вместо виртуального ~ A() {} в С++ 11?
В Qaru post Проверка типа объекта в С++ 11, у меня есть комментарий:
В С++ 11 вы действительно захотите сделать virtual ~A() = default;
В противном случае вы потеряете конструкторы перемещения implict.
Что такое virtual ~A() = default;
для? Как происходит неявное перемещение конструкторов, потерянных с помощью virtual ~A() {}
?
Ответы
Ответ 1
Комментарий неверен.
И
virtual ~A() = default;
и
virtual ~A() {}
пользователь объявлен. И неявные элементы перемещения блокируются, если объявлен деструктор.
[dcl.fct.def.default]/p4 обсуждает объявленные пользователем и специальные пользовательские элементы:
Специальная функция-член предоставляется пользователю, если она объявлена пользователем и явно не дефолт или не удаляется по его первой декларации.
Ответ 2
В этом сообщении fooobar.com/questions/110494/... у меня есть комментарий:
В С++ 11 вы действительно захотите сделать virtual ~A() = default;
В противном случае вы потеряете конструкторы перемещения implict.
Комментарий неверен.
Даже default
ed, деструктор "объявлен пользователем" (хотя обратите внимание, что он также не "предоставляется пользователем" ).
#include <iostream>
struct Helper
{
Helper() {}
Helper(const Helper& src) { std::cout << "copy\n"; }
Helper(Helper&& src) { std::cout << "move\n"; }
};
struct A
{
virtual ~A() {}
Helper h;
};
struct B
{
virtual ~B() = default;
Helper h;
};
struct C
{
Helper h;
};
int main()
{
{
A x;
A y(std::move(x)); // outputs "copy", because no move possible
}
{
B x;
B y(std::move(x)); // outputs "copy", because still no move possible
}
{
C x;
C y(std::move(x)); // outputs "move", because no user-declared dtor
}
}
+ g++ - 4.8 -std = С++ 11 -O2 -Wall -pthread main.cpp
+./a.out
копия
копия
переместить
Итак, вы не "потеряли" что-нибудь; здесь не было никаких функций перемещения.
Вот стандартный отрывок, запрещающий неявный конструктор перемещения в обоих случаях:
[C++11: 12.8/9]:
Если определение класса X
явно не объявляет конструктор перемещения, он будет объявлен неявным образом как дефолт, если и только если
-
X
не имеет объявленного пользователем конструктора копирования, -
X
не имеет объявленного пользователем оператора назначения копирования, -
X
не имеет объявленного пользователем оператора назначения перемещения, -
X
не имеет объявленного пользователем деструктора и - конструктор перемещения не будет неявно определен как удаленный.
Bootnote
Не помешало бы, если бы в будущей версии стандарта фактически были указаны точные значения таких терминов, как "объявленный пользователем". По крайней мере, это:
[C++11: 8.4.2/4]:
[..] Специальная функция-член предоставляется пользователю, если она объявлена пользователем и явно не дефолтна или удалена в ее первом объявлении. [..]
Здесь можно различать это различие.
Ответ 3
Этот комментарий неверен.
Вместо того, чтобы предоставлять свой собственный конструктор перемещения, если вы хотите, чтобы компилятор предоставил один, одно из требований заключается в том, что он ожидает, что деструктор также предоставлен им, т.е. тривиальным деструктором. Тем не менее, текущий стандарт довольно строгий, когда может быть предоставлена неявная реализация — в принятии того, как пользователь дает деструктор. Все, что объявлено пользователем, считается, что пользователь берет дело в свои руки и, следовательно, не только это
~A() { … }
но и этот
~A() = default;
заставляет компилятор не предоставлять неявный деструктор. Во-первых, это определение и, следовательно, декларация; вторая - это просто декларация. В обоих случаях деструктор объявляется пользователем и, таким образом, запрещает компилятору предоставлять неявный конструктор перемещения.
Я полагаю, что обоснование требования заключается в том, что во время перемещения ресурс объекта перемещается на другой объект, оставляя исходный объект в состоянии, когда у него нет ресурсов в динамическом хранилище; но если ваш класс не имеет таких ресурсов, то он может быть тривиально перемещен, уничтожен и т.д. Когда вы объявляете нетривиальный деструктор, это ключевой для компилятора, что ресурсы, которыми вы управляете в классе, не являются чем-то тривиальным и что вам в основном нужно было бы обеспечить нетривиальное перемещение, поэтому компилятор не предоставляет его.