Pierwotnie opublikowałem to jako pytanie tylko o destruktory, ale teraz dodaję do rozważenia domyślnego konstruktora. Oto oryginalne pytanie:
Jeśli chcę dać mojej klasie destruktor, który jest wirtualny, ale poza tym jest taki sam, jak wygenerowałby kompilator, mogę użyć
=default
:class Widget { public: virtual ~Widget() = default; };
Wygląda jednak na to, że ten sam efekt można uzyskać, mniej wpisując, używając pustej definicji:
class Widget { public: virtual ~Widget() {} };
Czy istnieje sposób, w jaki te dwie definicje zachowują się inaczej?
Na podstawie odpowiedzi przesłanych na to pytanie sytuacja domyślnego konstruktora wydaje się podobna. Biorąc pod uwagę, że nie ma prawie żadnej różnicy w znaczeniu między znakami „ =default
” i „ {}
” dla destruktorów, czy podobnie nie ma prawie żadnej różnicy w znaczeniu między tymi opcjami dla domyślnych konstruktorów? To znaczy, zakładając, że chcę utworzyć typ, w którym obiekty tego typu będą zarówno tworzone, jak i niszczone, dlaczego miałbym chcieć powiedzieć
Widget() = default;
zamiast
Widget() {}
?
Przepraszam, jeśli rozszerzenie tego pytania po jego oryginalnym opublikowaniu narusza niektóre zasady SO. Opublikowanie prawie identycznego pytania dla domyślnych konstruktorów wydało mi się mniej pożądaną opcją.
źródło
= default
jest imo bardziej dosłowne i jest zgodne z obsługą go przez konstruktorów.std::has_trivial_destructor<Widget>::value
dotyczytrue
pierwszego, alefalse
drugiego. Jakie są z tego konsekwencje, też nie wiem. :)Odpowiedzi:
To zupełnie inne pytanie, kiedy pytamy o konstruktory niż destruktory.
Jeśli twoim destruktorem jest
virtual
, różnica jest znikoma, jak zauważył Howard . Jeśli jednak twój destruktor nie był wirtualny , to zupełnie inna historia. To samo dotyczy konstruktorów.Używanie
= default
składni dla specjalnych funkcji składowych (domyślny konstruktor, konstruktory kopiowania / przenoszenia / przypisanie, destruktory itp.) Oznacza coś zupełnie innego niż zwykłe robienie{}
. W tym drugim przypadku funkcja staje się „dostarczana przez użytkownika”. A to wszystko zmienia.To trywialna klasa według definicji C ++ 11:
Jeśli spróbujesz utworzyć domyślny konstruktor, kompilator automatycznie wygeneruje domyślny konstruktor. To samo dotyczy kopiowania / przenoszenia i niszczenia. Ponieważ użytkownik nie dostarczył żadnej z tych funkcji składowych, specyfikacja C ++ 11 uważa tę klasę za „trywialną”. Dlatego jest to legalne, na przykład zapamiętywanie ich zawartości w celu ich zainicjowania i tak dalej.
To:
Jak sama nazwa wskazuje, nie jest to już trywialne. Ma domyślny konstruktor dostarczony przez użytkownika. Nie ma znaczenia, czy jest pusty; jeśli chodzi o reguły C ++ 11, nie może to być trywialny typ.
To:
Jak sama nazwa wskazuje, jest to trywialny typ. Czemu? Ponieważ powiedziałeś kompilatorowi, aby automatycznie wygenerował domyślny konstruktor. Dlatego konstruktor nie jest „udostępniany przez użytkownika”. Dlatego typ liczy się jako trywialny, ponieważ nie ma dostarczonego przez użytkownika domyślnego konstruktora.
= default
Składnia jest głównie tam robi takie rzeczy jak kopiowanie konstruktorów / cesja podczas dodawania funkcji składowych, które uniemożliwiają tworzenie takich funkcji. Ale wyzwala również specjalne zachowanie kompilatora, więc jest przydatne również w domyślnych konstruktorach / destruktorach.źródło
=default
funkcji) a funkcjami dostarczanymi przez użytkownika (co ma miejsce w przypadku{}
). Zarówno funkcje zadeklarowane przez użytkownika, jak i funkcje dostarczone przez użytkownika mogą zapobiegać generowaniu innych specjalnych funkcji składowych (np. Destruktor zadeklarowany przez użytkownika zapobiega generowaniu operacji przenoszenia), ale tylko funkcja specjalna dostarczona przez użytkownika sprawia, że klasa jest nietrywialna. Dobrze?= default
wydaje się być przydatny do wymuszania na kompilatorze wygenerowania domyślnego konstruktora pomimo obecności innych konstruktorów; domyślny konstruktor nie jest niejawnie zadeklarowany, jeśli podano inne konstruktory zadeklarowane przez użytkownika.Oba są nietrywialne.
Obie mają tę samą specyfikację noexcept w zależności od specyfikacji noexcept podstaw i elementów członkowskich.
Jedyną różnicą, którą do tej pory wykrywam, jest to, że jeśli
Widget
zawiera bazę lub element członkowski z niedostępnym lub usuniętym destruktorem:Wtedy
=default
rozwiązanie zostanie skompilowane, aleWidget
nie będzie typu zniszczalnego. To znaczy, jeśli spróbujesz zniszczyć plikWidget
, pojawi się błąd kompilacji. Ale jeśli tego nie zrobisz, masz działający program.Otoh, jeśli dostarczysz destruktor dostarczony przez użytkownika , to rzeczy się nie skompilują, niezależnie od tego, czy zniszczysz
Widget
:źródło
=default;
kompilator nie wygeneruje destruktora, chyba że zostanie użyty, a zatem nie spowoduje błędu. Wydaje mi się to dziwne, nawet jeśli niekoniecznie jest to błąd. Nie mogę sobie wyobrazić, że takie zachowanie jest wymagane w standardzie.Ważna różnica między
i
oznacza, że domyślny konstruktor zdefiniowany za pomocą
B() = default;
jest uważany za niezdefiniowany przez użytkownika . Oznacza to, że w przypadku inicjalizacji wartości, jak wnastąpi specjalny rodzaj inicjalizacji, który w ogóle nie używa konstruktora, a dla typów wbudowanych spowoduje to zerową inicjalizację . W takim przypadku
B(){}
nie nastąpi. Zgodnie z normą C ++ n3337 § 8.5 / 7Na przykład:
możliwy wynik:
http://ideone.com/k8mBrd
źródło