2 Stimmen

C++ Zeiger-Datenelemente: Wer sollte sie löschen?

Sagen wir, ich habe eine Klasse wie diese:

class A {
    public:
        A( SomeHugeClass* huge_object)
            : m_huge_object(huge_object) {}
    private:
        SomeHugeClass* m_huge_object;        
};

Wenn jemand den Konstruktor auf diese Weise verwendet:

A* foo = new A(new SomeHugeClass());

Wer ist dafür verantwortlich, das im Konstruktor neu angelegte Objekt zu löschen? In diesem Fall kann der Bereich, in dem der A-Konstruktor aufgerufen wurde, nur foo löschen, da die SomeHugeClass anonym ist.

Was aber, wenn jemand den Konstruktor wie folgt verwendet?

SomeHugeClass* hugeObj = new SomeHugeClass();
A* foo = new A(hugeObj);

Dann kann der Aufrufer delete hugeObj an einem bestimmten Punkt aufrufen, richtig?

Gibt es bei dieser Implementierung von A ein Speicherleck bei der Zerstörung?

Ich arbeite an einem Projekt, bei dem viele Objekte auf diese Weise komponiert werden, und so gerne ich auch intelligente Zeiger verwenden würde, muss ich mit den Projektleitern darüber sprechen, wie ich den alten Code ändern kann, um die Vorteile zu nutzen, bevor ich das kann.

8voto

zvone Punkte 16466

Ich versuche, diese einfache Regel zu befolgen, wann immer es möglich ist: Derjenige, der anruft new sollte anrufen delete auch. Andernfalls wird der Code bald zu unübersichtlich, um den Überblick darüber zu behalten, was gelöscht wird und was nicht.

In Ihrem Fall darf A::A, wenn es den Zeiger erhält, ihn nicht löschen. Denken Sie an diesen einfachen Fall:

SomeHugeClass* hugeObj = new SomeHugeClass();
A * a1 = new A(hugeObj);
A * a2 = new A(hugeObj);

Klasse A kann nicht wissen wer sonst noch diesen Zeiger benutzt!

Wenn Sie möchten, dass die Klasse A sich um den Zeiger kümmert, sollte sie ihn selbst erstellen.

Natürlich könnte man beide Fälle behandeln, aber das wäre vielleicht zu viel des Guten, etwa so:

A::A() : own_huge_object(true) {
    m_huge_object = new SomeHugeClass();
}
A::A(SomeHugeClass * huge_object ) : own_huge_object(false) {
    m_huge_object = huge_object;
}
A::~A() { if(own_huge_object) delete m_huge_object; }

3voto

Kirill V. Lyadvinsky Punkte 92957

In Ihrem Beispiel sollte der Anrufer für die Löschung der Daten verantwortlich sein. huge_object weil der Konstruktor eine Ausnahme auslösen könnte und der Destruktor von A wird nicht aufgerufen. Und Ihre Implementierung hat ein Speicherleck, denn niemand ruft delete jetzt.

Sie könnten verwenden shared_ptr wie folgt:

class A {
    public:
        A( shared_ptr<SomeHugeClass> huge_object)
            : m_huge_object(huge_object) {}
    private:
        shared_ptr<SomeHugeClass> m_huge_object;        
};

In diesem Fall sollten Sie sich nicht um das Löschen von SomeHugeClass .

0voto

Kevin Le - Khnle Punkte 9990

Damit diese Zeile unten

A* foo = new A(new SomeHugeClass());

nicht zu einem Speicherleck führen kann, können Sie sicherstellen, dass der Speicher, auf den die m_großes_Objekt im Destruktor der Klasse A.

Die Definition der Klasse A kann etwa wie folgt aussehen:

class A {
  public:
    A(SomeHugeClass* huge_object)
        : m_huge_object(huge_object) {}
    ~A() { delete m_huge_object; }
  private:
    SomeHugeClass* m_huge_object;        
};

Was mir daran nicht gefällt, ist, dass ich es vorziehe, dass derjenige, der Speicher zuweist, auch derjenige ist, der diesen Speicher wieder freigibt. Außerdem gibt es das Problem, auf das Kirill hingewiesen hat. Behalten Sie also Ihre Definition der Klasse A bei, und der Code kann so einfach sein wie:

SomeHugeClass* hugeObj = new SomeHugeClass();
A* foo = new A(hugeObj); 
//some code
delete hugeObj;
delete foo;

0voto

Alexandre C. Punkte 53706

Dies ist eher ein Designproblem. Wenn man traditionell Nicht-Konst-Zeiger an einen Konstruktor übergibt, sollte das Objekt den Zeiger freigeben. Übergeben Sie eine const-Referenz, wenn Sie beabsichtigen, eine Kopie zu behalten.

Ein alternativer Entwurf ist etwas in dieser Richtung: SomeHugeClass ist typischerweise nicht so groß (z.B. ein Zeiger), besitzt aber eine große Menge an Speicher:

class A
{
    SomeHugeClass m_;

public:
    A(SomeHugeClass x) { m_.swap(x); }
};

Dieses Design ist möglich, wenn SomeHugeClass einen effizienten Swap (Vertauschen der Zeiger) implementiert. Der Konstruktor erstellt eine Kopie von x bevor sie in m_ Wenn Sie ein temporäres Objekt an den Konstruktor übergeben, kann der Compiler die Kopie auslassen (und tut dies in der Regel auch).

Beachten Sie, dass SomeHugeClass hier ersetzt werden kann durch smart_pointer<SomeHugeClass> gibt die gewünschte Semantik, wenn Sie smart_pointer(new SomeHugeClass()) an den Konstruktor übergeben, da es sich um eine temporäre Klasse handelt.

EDIT (zur Verdeutlichung...): Der endgültige Code könnte wie folgt aussehen

class A
{
    smart_ptr<SHC> m_;

public:
    A(smart_ptr<SHC> x) { m_.swap(x); }
};

die das erforderliche Verhalten aufweist, wenn Sie die A(new SHC(...)) (keine Kopie und Löschung, wenn A zerstört wird), und wenn Sie A(smart_ptr<SHC>(a)) (Kopie von a durchgeführt und bei der Zerstörung von A wieder freigegeben wird).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X