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

11

01.04.2015, 09:52

Ok, jetzt bin ich doch ein wenig verwundert.

Wenn ich ein polymorphes Konstrukt nutze, sollte ich also den Base Klassen Destructor als virtual deklarieren? Wenn ich das nicht mache und meine derived Klasse deklariert ein normales int, habe ich ein Speicherleck, oder kommt das nur bei raw Pointern zum tragen?
Dementsprechend hätte ich hier ein Speicherleck?!:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
class Foo {
};

class Bar : public Foo {
int x;
};
Foo* ptr = new Bar();
delete ptr;


mfg

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »anti-freak« (01.04.2015, 10:02)


BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

12

01.04.2015, 09:55

Das allein reicht als Beispiel nicht aus. Es ist die Frage danach wie eine per new erzeugte Instanz gelöscht wird. Über einen Pointer auf die Base-Class oder auf einen Pointer auf die abgeleitete Klasse. Ersteres benötigt einen virtual dtor, letzteres nicht. Steht ja aber auch schon in den vorherigen Beiträgen.
In deinem Beispiel wirst Du aber kein Problem bekommen, da int keinen Destruktor rufen muss. Bei einem std::vector wäre es wieder was anderes.
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]

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

13

01.04.2015, 10:11

Das allein reicht als Beispiel nicht aus. Es ist die Frage danach wie eine per new erzeugte Instanz gelöscht wird. Über einen Pointer auf die Base-Class oder auf einen Pointer auf die abgeleitete Klasse. Ersteres benötigt einen virtual dtor, letzteres nicht. Steht ja aber auch schon in den vorherigen Beiträgen.
In deinem Beispiel wirst Du aber kein Problem bekommen, da int keinen Destruktor rufen muss. Bei einem std::vector wäre es wieder was anderes.

Doch, das Beispiel reicht aus; ohne virtuellen D'tor ist das undefiniertes Verhalten, da das delete den falschen D'tor aufruft.

Wenn ich ein polymorphes Konstrukt nutze, sollte ich also den Base Klassen Destructor als virtual deklarieren?

Wenn du ein Objekt per delete oder expliziten D'tor-Aufruf über einen Basepointer zerstören willst, muss die Basisklasse einen virtuellen D'tor haben. Immer. Selbst wenn die Klasse völlig leer wäre. Das ist aber auch der einzige Fall, in dem du einen virtuellen D'tor brauchst...

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.

Ja, Abstract Factories wären ein Beispiel wo man evtl. einen virtuellen D'tor würde haben wollen. Durch eine Abstract Factory erzeugte Objekte machen bei mir aber bei weitem nicht den Hauptanteil polymorpher Objekte aus. Ich brauch generell nur recht selten mal eine Factory und wenn, dann meistens, wenn es darum geht, Objekte über Modulgrenzen hinweg zu erzeugen und in dem Fall muss die Freigabe dann sowieso anders geregelt werden (z.B. über eine Release-Methode da potentiell verschiedene Runtime Environments). Abgesehen davon, ist es imo generell zu bevorzugen, dass der Client einer Abstract Factory nicht wissen muss, was genau für ein Allocator von der Factory verwendet wird (oder anders gesagt: dass der von einer konkreten Implementierung der Abstract Factory zu verwendende Memory Allocator nicht durch die Interface Definition auf ewig in Stein gemeißelt ist), was bei einem expliziten delete durch den Client Code und auch bei Return eines Smart Pointer in der Regel der Fall ist...

Dieser Beitrag wurde bereits 16 mal editiert, zuletzt von »dot« (01.04.2015, 10:30)


kiba

Alter Hase

  • »kiba« ist der Autor dieses Themas

Beiträge: 327

Wohnort: NRW

Beruf: Azubi: Fach-Info. Anw.

  • Private Nachricht senden

14

01.04.2015, 19:34

So wie ich das verstanden hab ist der protected non-virtual dazu da damit man eben nicht die Klasse so über die Basis Klasse löschen kann, was vll in den meisten fällen nicht notwendig ist.
Wenn man es aber doch versucht gibt es eine Compiler-Error und dann weis man "Ich sollte eine virtual D'tor benutzen".

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
// dtor is virtual, deleten möglich
FooException* foo_ptr = new FooException (1, "1 error");
FooBaseException* foo_base_ptr = dynamic_cast<FooException*>(foo_ptr); // or boost::..._downcast
delete foo_base_ptr;

// dtor ist protected non-virtual, deleten nicht möglich
//Foo* foo_ptr = new FooException (1, "1 error");
//Master* master_ptr = static_cast<Master*>(foo_ptr);
//delete master_ptr;



Nun mittlerweile glaub ich zu wissen wie man den D'tor einsetzen sollte.

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// nicht abstract, nix virtuales
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
};


// abstract
class BasePrinter {
    // muss virtual sein bei abstract    
    //protected:    
    //~BasePrinter();
    
    // theoretisch könnte man doch alles protected machen ?
    public:
    BasePrinter();

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

    virtual void 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
    void print(std::ostream& out) override;
};



// std::runtime_error hat virtual dtor
class FooBaseException : public std::runtime_error {
    protected:
    // info error stuff
    int _errc;

    public:

    FooBaseException(int errc, std::string msg)
        : std::runtime_error(msg)
        , _errc(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
    // virtual ~FooBaseException() = default;
    virtual ~FooBaseException();

    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)
    {}

    // 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
    ~FooException() override;

    // other ctors, methodes
};



Jetzt hab ich aber noch eine Warnung wo ich nicht ganz weiss wie ich die weg kriege.
Klar wenn ich ein dtor deklariere (der custom ist) sollte auch die rule-of-three/five deklarieren.
Problem ist hier das er den ausgelagerten D'tor (in .cpp) als custom ansieht.
Aber wenn ich diese auf = default; setzt krieg ich die -Wweak-vtables Warnung.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
warning: definition of implicit copy constructor for
      'FooException' is deprecated because it has a user-declared destructor
      [-Wdeprecated]
FooException::~FooException(){
              ^
 note: implicit copy constructor for 'FooException' first
      required here
    FooException new_foo_exc = foo_exc;  // or throw foo_exc;
                               ^
warning: definition of implicit copy constructor for
      'FooBaseException' is deprecated because it has a user-declared destructor
      [-Wdeprecated]
FooBaseException::~FooBaseException(){
                  ^
 note: implicit copy constructor for 'FooBaseException' first
      required here
class FooException : public FooBaseException {
      ^

Werbeanzeige