0

Die folgenden minimal-ish segfaults Programm, wenn es mit -O3 und vielleicht mit -O2 kompiliert, sondern führt mit -O0 (mit Klirren 4,0) fein:Detect oder tote Verweise auf temporäres vermeiden auf der Kompilierung

#include <iostream> 

class A { 
public: 
    virtual void me() const { std::cerr << "hi!\n"; } 
}; 

class B { 
public: 
    B(const A& a_) : a(a_) {} 
    virtual void me() const { a.me(); } 

private: 
    const A& a; 
}; 

class C { 
public: 
    C(const B& b_) : b(b_) {} 
    void me() const { b.me(); } 

public: 
    const B& b; 
}; 

int main() { 
    C c = C(A()); 
    c.me(); 
} 

Der Grund ist, dass mit einem Verweis auf ein temporäres Objekt der Klasse B initialisiert wird, das aus einem temporären A besteht. Nachdem der Konstruktor c.C() beendet wurde, ist das temporäre B verschwunden, aber die Referenz darauf bleibt in .

Welche guten Praktiken kann ich verwenden, um diese Situation zu vermeiden, da ich die Implementierung von B oder A nicht kontrolliere? Gibt es statische Analysatoren, die diesen Zustand erkennen können? (Meine Version von scan-build fand nicht das Problem.)

Verwandte: Detect dangling references to temporary

+0

Nun, Sie jetzt, rvalue Referenzen. Sie binden nur an Provisorien. Ich denke, das kann vielleicht als Lvalue-only-ref-wrapper abstrahiert werden. –

+0

Als allgemeine Regel deklarieren Sie keine Mitgliedsvariablen vom Typ reference. Verwenden Sie stattdessen einen Zeiger. Und wenn Sie einen Zeiger im Konstruktor bekommen, wird der resultierende Code offensichtlich sein. – rodrigo

+0

@rodrigo: Das klingt interessant.Ich denke, es würde auch funktionieren, wenn ich das Konstruktorargument nur als Zeiger deklariere und dann die Referenz initialisiere? Ich würde lieber * all * des Codes, der die Referenz verwendet, nicht umschreiben ... – krlmlr

Antwort

1

Ich persönlich mag Membervariablen nicht als Referenzen. Sie können nicht kopiert oder verschoben werden, und der Benutzer der Klasse muss sicherstellen, dass das Objekt die Referenz selbst überlebt. Eine Ausnahme könnte eine interne Hilfsklasse sein, die als temporäre Nur-Objekte, z. B. Funktoren, verwendet wird.

Ersetzen die Referenz mit einem Zeiger sollte einfach sein, und dann, wenn Sie auch einen Zeiger im Konstruktor verwenden:

class C { 
public: 
    C(const A *a_) : a(a_) {} 
private: 
    const A *a; 
}; 

... und so weiter. Wenn die Klasse sehr groß ist und man fühlt sich faul und wollen nicht das Mitglied ändern, können Sie nur den Konstruktor ändern:

class C { 
public: 
    C(const A *a_) : a(*a_) {} 
private: 
    const A &a; 
}; 

Um diese Klasse zu missbrauchen, da die OP sagt, müssen Sie etwas schreiben wie:

Und dann sollte der Fehler offensichtlich sein: einen Zeiger auf ein temporäres zu nehmen ist eine sehr schlechte Idee!

PS: Ich würde einen explicit zu Ihrem Konstruktor hinzufügen, nur um zu vermeiden, dass es an automatischen Konvertierungen teilnimmt, was meiner Meinung nach ein Teil Ihres Problems ist.

+0

Danke.Ich kann nicht ändern Klasse 'B' oder' A', wenn das meinst du durch "Explizit" hinzufügen – krlmlr

+0

@krimir Nein, ich meine ein explizites C (const A * a_) ', so dass der Konstruktor nicht implizit als Teil der Typkonvertierung aufgerufen wird, da einige Compilerfehler auftreten können unerwartete Aufrufe an Ihren Konstruktor – rodrigo

2

ich verschiedene Klassen von B und C ableiten würde (vielleicht sogar eine Vorlage-Klasse).

Diese Klassen enthalten ein nicht referenziertes Element, das zu dem Objekt wird, das sich auf a und b bezieht.

Ich würde dann die erforderlichen Kopierkonstruktoren/Zuweisungsoperatoren in diesen abgeleiteten Klassen implementieren, um dangling Verweise zu verhindern.

(Dann hätte ich eine robuste Konversation mit dem Autor von B und C).

+0

Dies ist für eine bestehende Code-Basis.Was ist ein guter Weg, um alle Verwendungen dieses Anti-Muster zu finden? – krlmlr