2014-11-18 16 views
19

Rozważmy następujący tekst:Dlaczego mogę zadeklarować obiekt z usuniętym destruktorem?

[C++11: 12.4/11]: destruktory są niejawnie wywoływany

  • dla budowanych obiektów o okres przechowywania statyczne (3.7.1) na zakończenie programu (3.6.3),
  • dla skonstruowana obiekty z czasem przechowywania wątku (3.7.2) przy wyjściu z gwintu,
  • dla obiektów skonstruowanych z automatycznym czasem przechowywania (3.7.3), gdy blok, w którym utworzono obiekt, wychodzi (6.7),
  • za wykonanie przejściowych obiektami, gdy żywotność tymczasowej końców przedmiotu (12,2),
  • za wykonanie obiektów przypisanych przez nowej ekspresji (5.3.4), poprzez użycie usunąć ekspresji (5,3. 5),
  • w kilku sytuacjach z powodu obsługi wyjątków (15.3).

Program jest źle sformułowany, jeśli zadeklarowano obiekt typu klasy lub jego tablicę, a destruktor klasy nie jest dostępny w miejscu deklaracji. Destruktory można również wywoływać jawnie.

Dlaczego program ten został pomyślnie skompilowany?

#include <iostream> 

struct A 
{ 
    A(){ }; 
    ~A() = delete; 
}; 

A* a = new A; 

int main() {} 

// g++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out 

Czy GCC jest po prostu dozwolone?


jestem skłonny powiedzieć tak, ponieważ odrzuca następujące jeszcze the standard appears to have no particular rule specific to deleted destructors in an inheritance hierarchy (tylko luźno odpowiednie sformułowanie jest adekwatne do pokolenia dotrzymali domyślnych konstruktorów):

#include <iostream> 

struct A 
{ 
    A() {}; 
    ~A() = delete; 
}; 

struct B : A {}; 

B *b = new B; // error: use of deleted function 

int main() {} 
+0

Nie ma czasu na standardowe nurkowanie, ale "dostępny" to nie to samo, co "nie usunięte". Jest publiczny. –

+0

@ R.MartinhoFernandes: Myślę, że wpadłem już w tę pułapkę. –

+1

To powiedziawszy, GCC nadal pozwala na to, nawet jeśli jest prywatne. –

Odpowiedz

12

Pierwsza część nie jest źle sformułowana, ponieważ standardowy tekst nie ma zastosowania - nie zadeklarowano tam żadnego obiektu typu A.

W drugiej części przyjrzyjmy się, jak działa konstrukcja obiektu. Norma mówi (15.2/2), że jeśli jakakolwiek część konstrukcji rzuca, wszystkie w pełni skonstruowane podobiekty do tego momentu są niszczone w odwrotnej kolejności od konstrukcji.

Oznacza to, że kod bazowego konstruktora, jeśli wszystkie wypisywane ręcznie, będzie wyglądać mniej więcej tak:

// Given: 
struct C : A, B { 
    D d; 
    C() : A(), B(), d() { /* more code */ } 
}; 

// This is the expanded constructor: 
C() { 
    A(); 
    try { 
    B(); 
    try { 
     d.D(); 
     try { 
     /* more code */ 
     } catch(...) { d.~D(); throw; } 
    } catch(...) { ~B(); throw; } 
    } catch(...) { ~A(); throw; } 
} 

Dla prostszej klasy, wymagany jest kod rozszerzony dla domyślnego konstruktora (którego definicja przez wyrażenie new) wyglądałby następująco:

B::B() { 
    A(); 
    try { 
    // nothing to do here 
    } catch(...) { 
    ~A(); // error: ~A() is deleted. 
    throw; 
    } 
} 

Wykonywanie tej pracy w przypadkach, gdy nie jest wyjątkiem mogą być wyrzucane po inicjalizacji ewentualnie jakiegoś podobiektu został wypełniony jest po prostu zbyt trudne do określenia. Dlatego to w rzeczywistości nie stało, ponieważ konstruktor domyślny dla B jest domyślnie zdefiniowany jako usunięte w pierwszej kolejności ze względu na ostatnim podpunkcie w N3797 12.1/4:

zalegającego domyślnego konstruktora dla klasy X jest zdefiniowany jako usunięty, jeśli:

  • [...]
  • każda bezpośrednia lub wirtualna klasa podstawowa lub niestatyczny element danych ma typ z destruktorem, który jest usunięty lub niedostępny z domyślnego domyślnego konstruktora.

Równoważnik język istnieje konstruktorów kopiowania/jako czwarte kuli 12,8/11.

Jest również ważnym w pkt 12.6.2/10:

W braku przekazaniu konstruktor, destruktor dla każdego bezpośredniego lub wirtualnej klasy bazowej i dla każdego nie-statyczne członka klasy typu danych jest potencjalnie wywoływana.

+0

Ooh, wyjątki są dobrym punktem. 12.6.2/10 wydaje się być tym, czego szukamy w [pierwotnym pytaniu] (http://stackoverflow.com/q/26986570/560648), ale nie ma go w C++ 11, tylko w C++ 14. –

+0

Czy nie byłoby słuszne rozważenie destruktorów wspomnianych w 12.6.2/10, które potencjalnie mogą być wywołane tylko wtedy, gdy konstruktor nie jest 'noexcept'? –

+0

@LightnessRacesinOrbit Paragraf jest rzeczywiście nowy dla C++ 14, ale ponieważ został wprowadzony przez raport o usterce (a dokładniej DR1424), nadal ma zastosowanie do C++ 11, a przynajmniej jest to generalnie opinia twórcy kompilatorów. http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1424 –

3

To takie B ' s destruktor jest generowany przez kompilator na linii twojego błędu i ma wywołanie destruktora A, które jest usuwane, stąd błąd. W pierwszym przykładzie nic nie próbuje wywołać destruktora A, więc nie ma błędu.

+0

Dlaczego 'B()' "ma wywołanie do" A " s destructor "? Nic nie znalazłem w standardzie, który to sugeruje i jest to sprzeczne z intuicją. –

+0

To ~ B(), które ma wywołanie (to destruktor klasy podstawowej) ~ A() –

+0

Jednak '~ B()' nigdy nie jest wywoływane. Tak więc według własnej logiki, która nie powinna mieć znaczenia. –

1

Accessibility is orthogonal to deletedness:

[C++11: 11.2/1]: Jeśli klasa jest zadeklarowany jako klasa bazowa (§ 10) do innej klasy, stosując specyfikator public dostępu, public członkowie klasy bazowej są dostępne jako public członków organów klasa pochodna i protected członkowie klasy bazowej są dostępni jako członkowie klasy pochodnej. Jeśli klasa jest zadeklarowana jako klasa podstawowa dla innej klasy przy użyciu specyfikatora dostępu protected, członkowie klasy bazowej są dostępni jako członkowie protected jako klasa pochodna. Jeśli klasa zostanie zadeklarowana jako klasa podstawowa dla innej klasy przy użyciu specyfikatora dostępu private, członkowie klasy bazowej będą dostępni jako członkowie private członków klasy pochodnej.

Jest to:

[C++11: 8.4.3/2]: Program, który odnosi się do usuniętej funkcji lub pośrednio, inne niż to uznania, jest słabo formowane. [Uwaga: Obejmuje to wywołanie funkcji niejawnie lub jawnie i utworzenie wskaźnika lub wskaźnika do elementu dla funkcji. Dotyczy to nawet odniesień w wyrażeniach, które nie są potencjalnie oceniane. Jeżeli funkcja jest przeciążona, odwołuje się do niej tylko wtedy, gdy wybrana funkcja jest wybrana z powodu przeciążenia. -mnotuj notatkę]

Ale nigdy nie "odnosić się" do usuniętego destruktora.

(I nadal nie można wyjaśnić, dlaczego przykład dziedziczenie nie kompiluje.)

3

Domyślam się, że to co się dzieje.

Domyślnie wygenerowany konstruktor B() najpierw skonstruuje podobiekt klasy podstawowej typu A. Język następnie stwierdza, że ​​jeśli wyjątek jest generowany podczas wykonywania treści konstruktora B(), podobiekt A musi zostać zniszczony. Stąd potrzeba dostępu do usuniętego ~A() - jest to formalnie potrzebne, gdy konstruktor rzuca. Oczywiście, ponieważ wygenerowana bryła B() jest pusta, nigdy nie może się to zdarzyć, ale wymagany jest dostęp do ~A().

Oczywiście, to jest 1) tylko odgadnięcie z mojej strony, dlaczego jest błąd w pierwszej kolejności i 2) nie w żaden sposób cytat normalizmu, aby powiedzieć, czy to faktycznie formalnie źle uformowane lub po prostu szczegół implementacji w gcc. Być może dałoby ci wskazówkę, gdzie w standardzie wyglądać ...

Powiązane problemy