Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

kiba

Alter Hase

  • »kiba« ist der Autor dieses Themas

Beiträge: 327

Wohnort: NRW

Beruf: Azubi: Fach-Info. Anw.

  • Private Nachricht senden

1

31.03.2015, 18:08

C++11/14: clang Warnung und virual Destructor

Hallo,
Es ist mal wieder eine bekannte Frage die immer wieder gestellt wird und ich die Antwort eigentlich kennen müsset.
"Wann benutzt man ein virtual dtor?"
Klar die BaseKlasse muss einen haben wenn diese von einer anderen Klasse geerbt wird, (oder nicht?)
Ich benutzt NIE den dtor, sonder deklariere immer ein leeren virtual dtor.
Kein new oder delete, alles über STL.
Und da ist auch die Frage, "WO definiert ich den virtual dtor".
Entweder im Header, ausgelagert in .cpp
oder virtual ~dtor() = default; im Header?

Denn ich bekomm in clang (3.5) eine Warnung, ich hab fast alle Warnungen eingeschaltet.

C-/C++-Quelltext

1
-Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-missing-braces


Warnungen über weak-vtables.

C-/C++-Quelltext

1
2
3
4
warning: 'Foo' has no out-of-line virtual method definitions;
      its vtable will be emitted in every translation unit [-Wweak-vtables]
class Foo : public Master<int> {
      ^


Beispiel:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BaseMaster {
   // attrs, stuff, ... nix virtuales
   public:
   BaseMaster();
   virtual ~BaseMaster();  // ~BaseMaster(){} in .cpp
};

template<class T>
class Master : public BaseMaster {
    // ... nix virtuales hier
    // attrs, shared_ptr<T>, ...
    public:
    Master(){}
    virtual ~Master() = default;
};

class Foo : public Master<U> {
  public:
  Foo();
};



Aktuelle mach ich das so, alle Klassen die geerbt werden bekommen ein virtual ~dtor().
virtual ~dtor(); mit .cpp in normalen Klassen.
virtual ~dtor() = default; in template-Klassen.

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

2

31.03.2015, 18:17

Zitat

Kein new oder delete, alles über STL.
Da hate's: Du benutzt den Destruktor!
Sobald ein Objekt Member hat, die keine primitiven Datentypen sind, gibt es Destruktoren auszuführen. Ein Beispiel:

C-/C++-Quelltext

1
2
3
4
5
6
7
class MyBaseObject {
public:
  // ...
protected:
  std::string name_;
  size_t id_;
};
Hier muss der Destruktor vorhanden sein, da dieser Destruktor implizit den Destruktor von name_ aufruft, wenn er fertig ist. Ansonsten: Wenn der Destruktor leer ist, kannste einfach das in den Header tippen:

C-/C++-Quelltext

1
2
3
4
5
class MyBaseObject {
public:
  virtual ~MyBaseObject() = default;
  // ...
};
Der Compiler fügt dann automatisch die Standard-Implementierung ein. Ist das gleiche, als würdest du virtual ~MyBaseObject() {} tippen, bloß dass default besser die Absicht vermittelt. Zudem kannste nicht einfach eine virtuelle Funktion einer Basisklasse durch eine nicht-virtuelle ersetzen, wie du es in Foo tust! Schreib also eine Option von den Zweien:

C-/C++-Quelltext

1
2
virtual ~Foo();
~Foo() override;

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

kiba

Alter Hase

  • »kiba« ist der Autor dieses Themas

Beiträge: 327

Wohnort: NRW

Beruf: Azubi: Fach-Info. Anw.

  • Private Nachricht senden

3

31.03.2015, 18:38

Der Compiler fügt dann automatisch die Standard-Implementierung ein. Ist das gleiche, als würdest du virtual ~MyBaseObject() {} tippen, bloß dass default besser die Absicht vermittelt.


Ja ich weis das der automatisch den dtor generiert, ich meinte nur das ich keinen dtor-custom schreibe um Speicher oder anderes zu löschen.

Zudem kannste nicht einfach eine virtuelle Funktion einer Basisklasse durch eine nicht-virtuelle ersetzen, wie du es in Foo tust!


Ahh ok da war der Fehler, ich bin nämlich davon ausgegangen das man nur in den BaseKlassen den virtual dtor deklarieren muss aber nicht mehr in den weiteren Klassen.
Dachte der Kompiler würde das überschreiben des dtor übernehmen.

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

4

31.03.2015, 19:03

Zitat

Klar die BaseKlasse muss einen haben wenn diese von einer anderen Klasse geerbt wird, (oder nicht?)

Oder nicht.
Ein virtueller Destruktor ist nicht notwendig, wenn du nur vererbst. Er ist erst notwendig, wenn du ein Objekt vom abgeleiteten Typ durch einen Pointer löscht, der nur auf die Basisklasse verweist. (Klar, die Member des abgeleiteten Typen müssen zerstört werden und der Basisklassendestruktor weiß davon nichts. Wenn du einen abgeleiteten Typ direkt verwendest, wird natürlich auch dessen Destruktor direkt aufgerufen. Virtuell muss er dann noch nicht sein.)

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

5

31.03.2015, 19:11

Es ist mal wieder eine bekannte Frage die immer wieder gestellt wird und ich die Antwort eigentlich kennen müsset.
"Wann benutzt man ein virtual dtor?"
Klar die BaseKlasse muss einen haben wenn diese von einer anderen Klasse geerbt wird, (oder nicht?)

Nein; leider wird sehr oft gelehrt, dass eine Klasse, von der geerbt wird, einen virtuellen Destruktor haben sollte. Das ist allerdings nicht wirklich notwendig, potentiell performanceschädlich und imo als Bad Practise einzustufen. Ein virtueller Destruktor wird (wie das eben generell mit virtuellen Methoden so ist) nur dann benötigt, wenn auf irgendeine Art der Destruktor einer abgeleiteten Klasse über einen Basisklassenzeiger augerufen werden soll; z.B. weil ein Objekt einer abgeleiteten Klasse über einen Basisklassenzeiger deleted werden soll wie z.B. hier:

C-/C++-Quelltext

1
2
Base* bla = new Derived;
delete bla;

oder aber auch hier:

C-/C++-Quelltext

1
std::unique_ptr<Base> bla(new Derived);


Ich würde als Faustregel empfehlen, Destruktoren von Basisklassen standardmäßig non-virtual aber protected zu machen (per = default;). Damit verhinderst du, dass versehentlich wo ein delete über einen Basepointer durchgeführt werden kann. Nur bei Klassen, wo die Möglichkeit eines delete über einen Basepointer tatsächlich gegeben sein muss (meiner Erfahrung nach eher selten), würde ich einen public virtual Desktruktor definieren. Und es reicht, des Destruktor dann in der Basisklasse virtual zu deklarieren, das macht in der Tat autom. den Destruktor aller abgeleiteten Klassen virtual.

Ich benutzt NIE den dtor, sonder deklariere immer ein leeren virtual dtor.

Ganz schlechte Idee; damit machst du jede Klasse automatisch zu einem polymorphen Typ mit all dem damit verbundenen Overhead (jede solche Klasse erzeugt eine vtable, jedes Objekt einer solchen Klasse enthält einen zusätzlichen vtable Pointer, der bei Objektkonstruktion initialisiert werden muss etc.) auch wenn dies gar nicht notwendig wäre. Und Visual C++ beispielsweise generiert in manchen Situationen suboptimalen Code, sobald ein benutzerdeklarierter Konstruktor in der Nähe ist (selbst wenn dieser leer oder = default; ist), daher ist es empfehlenswert, wann immer möglich, sich auf die vom Compiler automatisch generierten Konstruktoren und Destruktoren zu verlassen.


Der Compiler fügt dann automatisch die Standard-Implementierung ein. Ist das gleiche, als würdest du virtual ~MyBaseObject() {} tippen, bloß dass default besser die Absicht vermittelt.

Das stimmt nicht ganz, der compilergenerierte Destruktor tut zwar effektiv in der Regel das selbe wie einfach ein Destruktor mit leerer Definition, genießt aber dennoch eine Sonderstellung, beispielsweise in der Definition von trivial kopierbaren Datentypen.

Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von »dot« (31.03.2015, 19:26)


Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

6

31.03.2015, 20:33

Jetzt bin ich neugierig, worin diese "Sonderstellung" besteht. Wüsste von keiner. ö.ö

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

7

31.03.2015, 21:37

Nur bei Klassen, wo die Möglichkeit eines delete über einen Basepointer tatsächlich gegeben sein muss (meiner Erfahrung nach eher selten), würde ich einen public virtual Desktruktor definieren.
Solche oder ähnliche Aussagen lese ich von dir erstaunlich oft. Du verwendest nur selten Polymorphie, oder? Während das in vielen Business-Anwendungen tatsächlich nicht oft benötigt wird, ist es bei Spielen doch schon eher mal an der Tagesordnung.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

kiba

Alter Hase

  • »kiba« ist der Autor dieses Themas

Beiträge: 327

Wohnort: NRW

Beruf: Azubi: Fach-Info. Anw.

  • Private Nachricht senden

8

31.03.2015, 22:59

Nein; leider wird sehr oft gelehrt, dass eine Klasse, von der geerbt wird, einen virtuellen Destruktor haben sollte. Das ist allerdings nicht wirklich notwendig, potentiell performanceschädlich und imo als Bad Practise einzustufen. Ein virtueller Destruktor wird (wie das eben generell mit virtuellen Methoden so ist) nur dann benötigt, wenn auf irgendeine Art der Destruktor einer abgeleiteten Klasse über einen Basisklassenzeiger augerufen werden soll; z.B. weil ein Objekt einer abgeleiteten Klasse über einen Basisklassenzeiger deleted werden soll

Genau so hab ich das auch gelernt.
Eben mit den Hintergrundwissen das wenn jemand die Objekte verwaltet (ownship hat), z.B. über std::vector<BasisKlase*> dann diese deleted kann.
Wobei man heute schon shared_ptr und weak_ptr nutzen kann, was das ownshipen um vieles vereinfacht und der dtor eben nur noch selten selber geschrieben werden muss wenn es um solchen Sachen geht.


Ich würde als Faustregel empfehlen, Destruktoren von Basisklassen standardmäßig non-virtual aber protected zu machen (per = default;). Damit verhinderst du, dass versehentlich wo ein delete über einen Basepointer durchgeführt werden kann. Nur bei Klassen, wo die Möglichkeit eines delete über einen Basepointer tatsächlich gegeben sein muss (meiner Erfahrung nach eher selten), würde ich einen public virtual Desktruktor definieren. Und es reicht, des Destruktor dann in der Basisklasse virtual zu deklarieren, das macht in der Tat autom. den Destruktor aller abgeleiteten Klassen virtual.


Ok also kann ich einfach als Faustregel nehmen:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// nicht abstract, nichts virtual
class Master {
    protected:
    // attrs

    // wird von anderen Klasse geerbt, dtor deklarieren
    ~Master() = default;

    public:
    Master();
    // Master(...);

    void dostuff();
};

class Foo : public Master {
    public:
    Foo();
    // kein dtor override notwendig
};



Ich hab noch etwas weiter rum probiert, wie es denn mit virtual Methoden aussieht:
Dort muss ich virtual dtor nehmen, wegen der Warnung: non-virtual-dtor

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// abstract
class BasePrinter {
    // muss virtual sein bei abstract    
    //protected:    
    //~BasePrinter();
    
    // theoretisch könnte man doch alles protected machen ?
    public:
    BasePrinter();
    virtual ~BasePrinter();

    virtual std::string print(std::ostream& out) = 0;
};


class IntPrinter : public BasePrinter {
    public:
    IntPrinter();
    // kein dtor override notwendig ?
    // warning: 'IntWriter' has virtual functions but non-virtual destructor [-Wnon-virtual-dtor]
    // virtual dtor in public, nicht non-virtual dtor in protected
    virtual ~IntPrinter() = default;


    // override virtual methode
    std::string print(std::ostream& out) override;
};



Zudem krieg ich noch die Warnung, weak-vtables:
Wenn es einen virtual dtor gibt und die abgeleiteten Klasse kein dtor hat:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// std::runtime_error hat virtual dtor
class FooBaseException : public std::runtime_error {
    protected:
    // info error stuff
    int _errc;

    // warning: 'FooBaseException' has no out-of-line virtual method
    // definitions; its vtable will be emitted in every translation unit
    // [-Wweak-vtables]
    // dtor auslagern nach .cpp
    ~FooBaseException();

    public:

    FooBaseException(int errc, std::string msg)
        : std::runtime_error(msg)
        , _errc(errc)
    {}

    int getErrc() const { return this->_errc; }
};


// warning: 'FooException' has no out-of-line virtual method
// definitions; its vtable will be emitted in every translation unit
// [-Wweak-vtables]
class FooException : public FooBaseException {
    public:
    FooException(int errc, std::string msg)
        : FooBaseException(errc, msg)
    {}

    // dtor override um warnung zu beheben
    // dtor auslagern nach .cpp
    ~FooException() override;

    // other ctors, methodes
};

Bei templates kann ich einfach ~dtor() = default; benutzten, da gibt es keine Warnung, nur bei einer abgeleiteten Klasse die kein template ist muss ich den dtor wieder auslagern.
http://stackoverflow.com/questions/23746…s-wweak-vtables




Also noch mal Zusammengefasst:
  • Bei non-virtual Basis Klasse: protected non-virtual dtor verwenden
  • virtual dtor in Ausnahmefällen verwenden z.B. selber Verwalten von Objekten mit delete und BasisKlasse
  • Wenn es geht dtor() = default; verwenden
  • Bei (abstrakter) Basis Klasse mit virtual Methoden: virtual dtor verwenden (- Wnon-virtual-dtor Warnung)
  • virtual Methoden (oder Destruktor) deklarieren und in .cpp definieren (-Wweak-vtables Warnung)
  • Hat die Basis Klasse ein virtual dtor, sollte der dtor in der abgeleiteten Klasse überschreiben werden, ~dtor() override; (-Wweak-vtables Warnung)

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

9

01.04.2015, 02:22

Jetzt bin ich neugierig, worin diese "Sonderstellung" besteht. Wüsste von keiner. ö.ö

Ein Beispiel hab ich ja schon genannt: trivial kopierbare Datentypen. Sobald eine Klasse einen benutzerdefinierten Destruktor hat, selbst wenn dieser leer ist, können Objekte dieses Typs nichtmehr byteweise kopiert werden; mit dem impliziten Destruktor ist dies dagegen erlaubt, sofern noch ein paar andere Bedingungen erfüllt sind. Ein weiteres Beispiel wären PODs, die können auch keinen benutzerdefinierten D'tor haben, selbst wenn dieser leer wäre. In Visual C++ kann man außerdem beobachten, dass der Compiler in manchen Situationen wesentlich schlechteren Code erzeugt, sobald ein benutzerdefinierter D'tor vorhanden ist, selbst wenn dieser leer ist...

Nur bei Klassen, wo die Möglichkeit eines delete über einen Basepointer tatsächlich gegeben sein muss (meiner Erfahrung nach eher selten), würde ich einen public virtual Desktruktor definieren.
Solche oder ähnliche Aussagen lese ich von dir erstaunlich oft. Du verwendest nur selten Polymorphie, oder? Während das in vielen Business-Anwendungen tatsächlich nicht oft benötigt wird, ist es bei Spielen doch schon eher mal an der Tagesordnung.

Doch, ich verwende regelmäßig Polymorphie und bin im Bereich High-Performance-Graphics unterwegs, schreib also keine Business-Anwendungen (für solche würde man vermutlich auch eher kaum C++ benutzen). Den Fall, dass ich polymorphes delete benötige, habe ich relativ selten, da der Besitzer eines Objektes meistens den konkreten Typ kennt, auch wenn das Objekt an anderen Stellen polymorph verwendet wird...

Ich hab noch etwas weiter rum probiert, wie es denn mit virtual Methoden aussieht:
Dort muss ich virtual dtor nehmen, wegen der Warnung: non-virtual-dtor

Wenn ich das richtig sehe, sollte das nur eine Warning geben, wenn eine Klasse virtuelle Methoden und einen non-virtual D'tor hat und der D'tor der Basisklasse nicht protected ist.

Zudem krieg ich noch die Warnung, weak-vtables:
Wenn es einen virtual dtor gibt und die abgeleiteten Klasse kein dtor hat:

Wenn ich das richtig sehe, bekommst du die Warning, wenn eine Klasse virtuelle Methoden hat und alle Methoden der Klasse inline sind. Du musst einfach nur irgendeine Methode in einem .cpp File implementieren. Wenn der D'tor nichts besonderes tut, würde ich ihn nicht einmal deklarieren, sondern den impliziten verwenden.

Also noch mal Zusammengefasst:
  • Bei non-virtual Basis Klasse: protected non-virtual dtor verwenden
  • virtual dtor in Ausnahmefällen verwenden z.B. selber Verwalten von Objekten mit delete und BasisKlasse
  • Wenn es geht dtor() = default; verwenden
  • Bei (abstrakter) Basis Klasse mit virtual Methoden: virtual dtor verwenden (- Wnon-virtual-dtor Warnung)
  • virtual Methoden (oder Destruktor) deklarieren und in .cpp definieren (-Wweak-vtables Warnung)
  • Hat die Basis Klasse ein virtual dtor, sollte der dtor in der abgeleiteten Klasse überschreiben werden, ~dtor() override; (-Wweak-vtables Warnung)
  • Bei Klassen im Allgemeinen sollte man einen Destruktor nur deklarieren, wenn es notwendig ist und ansonsten den impliziten Destruktor verwenden.
  • Bei Basisklassen würde ich prinzipiell einen non-virtual protected Destruktor machen so lange nichts anderes benötigt wird, wenn möglich = default.
  • Einen virtual D'tor verwendest du nur dann, wenn es notwendig ist.
  • Für abstrakte Basisklassen gilt das selbe wie sonst: virtual D'tor nur wenn polymorphes delete erwünscht ist.
  • Hat die Basisklasse einen virtual D'tor, muss dieser in der abgeleiteten Klasse nur explizit überschrieben werden, wenn das Defaultverhalten nicht ausreichend ist (der implizite D'tor ist dann automatisch virtual).
  • An virtuellen Destruktoren ist nichts Besonderes dran. Einen virtuellen Destruktor brauchst du, wenn du über einen Basisklassenzeiger den Destruktor des abgeleiteten Objektes aufrufen musst. Das ist alles. Wenn du das nicht brauchst, brauchst du keinen virtuellen Destruktor. Eine Klasse die keinen virtuellen Destruktor braucht, sollte keinen virtuellen Destruktor haben.

Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von »dot« (01.04.2015, 02:36)


BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

10

01.04.2015, 07:19

Den Fall, dass ich polymorphes delete benötige, habe ich relativ selten, da der Besitzer eines Objektes meistens den konkreten Typ kennt, auch wenn das Objekt an anderen Stellen polymorph verwendet wird...
Hmm, kann ich leider so nicht bestätigen. Das stimmt nach meiner Erfahrung nur solange, wie man nur ein konkretes Objekt hat. Hat man aber mehrere oder gar eine Factory (die ein Objekt liefert und gleichzeitig auch die Ownership zurück überträgt), weiß man hinterher eigentlich nicht mehr was ursprünglich mal wie erzeugt wurde. So ein Fall scheint bei Spielen doch sehr üblich zu sein - Entities (NPCs/Monster/etc), AI-Typen, Ausrüstungen, etc.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Werbeanzeige