Czy' pusty ' konstruktor lub Destruktor zrobi to samo co wygenerowany?

Załóżmy, że mamy (zabawkę) klasę C++ taką jak:

class Foo {
    public:
        Foo();
    private:
        int t;
};

Ponieważ nie zdefiniowano destruktora, kompilator C++ powinien utworzyć go automatycznie dla klasy Foo. Jeśli Destruktor nie musi czyścić żadnej dynamicznie alokowanej pamięci (tzn. możemy rozsądnie polegać na destruktorze, który nam daje kompilator), zdefiniuje pusty Destruktor, tj.

Foo::~Foo() { }

Zrobić to samo co wygenerowany kompilator? A co z pustym konstruktorem-że jest, Foo::Foo() { }?

Jeśli istnieją różnice, to gdzie one istnieją? Jeśli nie, to czy jedna metoda jest preferowana nad drugą?

Author: ローウ, 2009-06-22

7 answers

Zrobi to samo (w zasadzie nic). Ale to nie to samo, jakbyś tego nie napisał. Ponieważ napisanie destruktora będzie wymagało działającego destruktora klasy bazowej. Jeśli Destruktor klasy bazowej jest prywatny lub istnieje inny powód, dla którego nie można go wywołać, to twój program jest uszkodzony. Rozważmy to

struct A { private: ~A(); };
struct B : A { }; 

To jest OK, o ile nie wymagasz niszczenia obiektu typu B (a więc domyślnie typu A) - jak jeśli nigdy nie wywołasz delete na dynamicznie utworzony obiekt, lub nigdy nie tworzysz z niego obiektu w pierwszej kolejności. Jeśli to zrobisz, kompilator wyświetli odpowiednią diagnostykę. Teraz jeśli podasz jeden jawnie

struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } }; 

Ten spróbuje w domyśle wywołać Destruktor klasy bazowej i spowoduje diagnostykę już w momencie definicji ~B.

Istnieje inna różnica, która koncentruje się wokół definicji destruktora i niejawnych wywołań do destruktorów członkowskich. Rozważ ten inteligentny wskaźnik członek

struct C;
struct A {
    auto_ptr<C> a;
    A();
};

Załóżmy, że obiekt typu C jest tworzony w definicji konstruktora a w pliku .cpp, który zawiera również definicję struct C. Jeśli używasz struct A i wymagasz zniszczenia obiektu A, kompilator dostarczy ukrytą definicję destruktora, tak jak w powyższym przypadku. Ten Destruktor będzie również domyślnie wywoływał Destruktor obiektu auto_ptr. A to usunie trzymany przez niego wskaźnik, który wskazuje na obiekt C - nie znając definicji C! Które pojawiły się w pliku .cpp, w którym zdefiniowany jest konstruktor struct A.

Jest to częsty problem przy implementacji idiomu pimpl. Rozwiązaniem jest dodanie destruktora i podanie jego pustej definicji w pliku .cpp, gdzie zdefiniowana jest struktura C. W momencie wywołania destruktora swojego elementu, będzie on znał definicję struct C i będzie mógł poprawnie wywołać Destruktor.
struct C;
struct A {
    auto_ptr<C> a;
    A();
    ~A(); // defined as ~A() { } in .cpp file, too
};

Zauważ, że boost::shared_ptr nie ma tego problemu: zamiast tego wymaga typu pełnego, gdy jego konstruktor jest wywoływany w określony sposób.

Innym punktem, w którym robi to różnicę w obecnym C++ jest to, kiedy chcesz użyć memset i znajomych na takim obiekcie, który ma zadeklarowany przez użytkownika Destruktor. Takie typy nie są już PODs (zwykłe stare dane) i nie mogą być kopiowane bitowo. Zauważ, że to ograniczenie nie jest tak naprawdę potrzebne - a następna wersja C++ ma Poprawiono sytuację w tym zakresie, dzięki czemu można nadal kopiować bitowo takie typy, o ile nie zostaną wprowadzone inne ważniejsze zmiany.


Skoro prosiłeś o konstruktorów: cóż, dla tych samych rzeczy są prawdziwe. Zauważ, że konstruktory zawierają również ukryte wywołania destruktorów. Na rzeczach takich jak auto_ptr, wywołania te (nawet jeśli nie są wykonywane w czasie wykonywania - czysta możliwość już tu ma znaczenie) wyrządzą taką samą szkodę jak dla destruktorów i zdarzają się, gdy coś w konstruktor rzuca-kompilator jest wtedy zobowiązany do wywołania destruktora członów. ta odpowiedź wykorzystuje w pewnym stopniu domyślną definicję konstruktorów domyślnych.

To samo dotyczy widoczności i PODness, które powiedziałem o destruktorze powyżej.

Jest jedna ważna różnica w inicjalizacji. Jeśli umieścisz konstruktor zadeklarowany przez użytkownika, Twój typ nie otrzyma już inicjalizacji wartości członków i zależy to od Twojego konstruktor, aby wykonać dowolną inicjalizację, która jest potrzebna. Przykład:

struct A {
    int a;
};

struct B {
    int b;
    B() { }
};

W tym przypadku, następujące jest zawsze prawdziwe

assert(A().a == 0);

Podczas gdy poniższe zachowanie jest niezdefiniowane, ponieważ b nigdy nie zostało zainicjowane (Twój konstruktor pominął to). Wartość może być zerowa, ale może również być dowolną inną dziwną wartością. Próba odczytu z tak niezainicjowanego obiektu powoduje nieokreślone zachowanie.

assert(B().b == 0);

Jest to również prawdziwe w przypadku używania tej składni w new, Jak new A() (zwróć uwagę na nawiasy na końcu-jeśli zostaną pominięte, inicjalizacja wartości nie jest wykonywana, a ponieważ nie ma konstruktora zadeklarowanego przez użytkownika, który mógłby ją zainicjować, a pozostanie niezainicjalizowana).

 118
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:44

Wiem, że jestem spóźniony w dyskusji, jednak moje doświadczenie mówi, że kompilator zachowuje się inaczej w obliczu pustego destruktora w porównaniu do generowanego przez kompilator. Przynajmniej tak jest w przypadku MSVC++ 8.0 (2005) i MSVC++ 9.0 (2008).

Patrząc na wygenerowany zestaw kodu wykorzystujący szablony wyrażeń, zdałem sobie sprawę, że w trybie release, wywołanie do mojego BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs) nigdy nie było wbudowane. (proszę nie zwracać uwagi na dokładne typy i operatora podpis).

Aby jeszcze bardziej zdiagnozować problem, włączyłem różne ostrzeżenia kompilatora , które są domyślnie wyłączone. c4714 ostrzeżenie jest szczególnie interesujące. Jest emitowana przez kompilator, gdy funkcja oznaczona __forceinline mimo to nie jest inlined .

Włączyłem Ostrzeżenie C4714 i oznaczyłem operator {[2] } i mogłem zweryfikować raporty kompilatora, że nie był w stanie połączyć się z operatorem.

Wśród powodów opisywany w dokumentacji kompilator nie wpisuje funkcji oznaczonej symbolem __forceinline dla:

Funkcje zwracające obiekt nieusuwalny według wartości, gdy-GX / EHs / EHa jest włączone

Tak jest w przypadku mojego BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs). BinaryVectorExpression jest zwracana przez wartość i mimo że jej Destruktor jest pusty, sprawia, że zwracana wartość jest uważana za obiekt nie do odzyskania. Dodanie throw () do destruktora nie pomogło kompilatorowi i i tak unikam używania specyfikacji WYJĄTKÓW . Komentowanie pustego destruktora pozwala kompilatorowi na pełną inline kodu.

Wyjściem jest to, że od teraz, w każdej klasie, piszę skomentowane puste destruktory, aby dać ludziom znać, że Destruktor nie robi nic celowo, tak samo jak ludzie komentują pustą specyfikację wyjątku ' / * throw ()*/, aby wskazać, że Destruktor nie może rzucić.

//~Foo() /* throw() */ {}
Mam nadzieję, że to pomoże.
 18
Author: Gregory Pakosz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-23 22:41:44

Pusty Destruktor zdefiniowany poza klasą ma podobną semantykę w większości przypadków, ale nie we wszystkich.

Konkretnie, niejawnie zdefiniowany Destruktor
1) jest inline Public member (twój nie jest inline)
2) jest oznaczany jako trywialny Destruktor (niezbędny do tworzenia trywialnych typów, które mogą być w związkach, twój nie może)
3) posiada specyfikację wyjątku (throw(), twoja nie)

 12
Author: Faisal Vali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-22 03:04:07

Tak, ten pusty Destruktor jest taki sam jak automatycznie generowany. Zawsze pozwalałem kompilatorowi generować je automatycznie; nie sądzę, aby konieczne było jawne określanie destruktora, chyba że trzeba zrobić coś niezwykłego: uczynić go wirtualnym lub prywatnym, powiedzmy.

 8
Author: David Seiler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-22 02:53:52

Zgadzam się z Davidem z tym wyjątkiem, że ogólnie dobrą praktyką jest definiowanie Wirtualnego destruktora tzn.

virtual ~Foo() { }

Pominięcie Wirtualnego destruktora może prowadzić do wycieku pamięci, ponieważ ludzie, którzy dziedziczą po twojej klasie Foo, mogą nie zauważyć, że ich Destruktor nigdy nie zostanie wywołany!!

 3
Author: oscarkuo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-22 09:31:47

Najlepiej umieścić pustą deklarację, mówi ona przyszłym opiekunom, że to nie było przeoczenie, a naprawdę chciałeś użyć domyślnej.

 1
Author: Ape-inago,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-22 03:04:33

Pusta definicja jest w porządku, ponieważ definicja może być odwołana

virtual ~GameManager() { };
pusta deklaracja jest zwodniczo podobna wyglądem
virtual ~GameManager();
, ale nie ma definicji dla wirtualnego destruktora błąd
Undefined symbols:
  "vtable for GameManager", referenced from:
      __ZTV11GameManager$non_lazy_ptr in GameManager.o
      __ZTV11GameManager$non_lazy_ptr in Main.o
ld: symbol(s) not found
 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-14 06:18:20