2016-04-02 6 views
2

Ich möchte eine reine abstrakte Basisklasse mit einem virtuellen Destruktor deklarieren. Ich kenne drei Wege, dies zu tun, aber ich weiß nicht, welches der beste ist oder warum.Best Practice für die Implementierung einer Schnittstelle wie reine abstrakte Basisklasse in C++?

Mein Ziel ist es, abstrakte Basisklassenschnittstellen im Best-Practice-C++ 11-Stil mit optimaler Laufzeitleistung zu implementieren. Insbesondere möchte ich es mir erlauben, No-Op-Destruktoren einzubinden/zu eliminieren. Außerdem möchte ich Warnungen in Bezug auf duplizierte VTables eliminieren, indem Sie entweder eine Implementierung auswählen, bei der die Duplikat-VTables nicht generiert werden, oder eine fundierte Entscheidung treffen, die Warnungen zu unterdrücken.

Hier sind die drei Möglichkeiten, um eine abstrakte Basisklasse zu implementieren, die ich kenne:

Option # 1

/// A.h: 

class A { 
public: 
    virtual ~A() {} 
    virtual int f() = 0; 
}; 

Option # 2

/// A.h: 

class A { 
public: 
    virtual ~A(); 
    virtual int f() = 0; 
}; 

/// A.cpp: 

A::~A() {} 

Option # 3

/// A.h: 

class A { 
public: 
    virtual ~A() = default; 
    virtual int f() = 0; 
}; 

Sind das meine einzigen Optionen?

Welche von # 1, # 2, # 3 gilt als Best Practice? Wenn Kompromisse bestehen (z. B. Laufzeit oder Kompilierungszeit), beschreiben Sie sie bitte.

Mit Option # 1 wird der Inline-Destruktor jemals inline?

Ich verstehe, dass Option 1 eine VTable in jede Übersetzungseinheit einfügt. Option # 1 generiert die -Wweak-vtables Warnung in clang und wird von der Kategorie "vage linkage" in gcc [1] abgedeckt. Option # 3 generiert keine Clam-Warnung - Bedeutet dies, dass Option 3 keine vtable generiert?

Wie unterscheidet sich die Option # 3 von den anderen Optionen?

Andere Fragen haben ähnliche Probleme in Bezug auf die Warnungen von Clang diskutiert, aber ich konnte keine Frage finden, die speziell darauf abzielte, welche Option als beste Praxis angesehen wird und warum.

[1] https://gcc.gnu.org/onlinedocs/gcc/Vague-Linkage.html

+2

Die [CppCoreGuidelines] (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md) hat einen guten Abschnitt über diese Art von Frage und könnte es wert sein Sie zu lesen. – Maikel

+0

Ich mache wissenschaftliche Berechnungen und in meinem Bereich wird keine dieser Varianten für Polymorphie verwendet. Für wirklich performance-kritische Aufgaben schauen Sie sich [statischer Polymorphismus] (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) oder den [Barton-Nackman-Trick] (https://en.wikipedia.org/wiki) an/Barton% E2% 80% 93Nackman_trick) – Maikel

+0

@Maikel Ja, ich benutze diese Techniken auch. Aber diese Frage bezieht sich auf die altmodische vtable-basierte Polymorphie-Technik. –

Antwort

1

best practice (zumindest, wenn ich verantwortlich bin):

struct A { 

    // 
    // keep move semantics available - rule of 0, 3, or 5 
    // in this case, 5 because we defined a destructor. 
    // 
    A(const A&) = default; 
    A(A&&) = default; 
    A& operator=(const A&) = default; 
    A& operator=(A&&) = default; 
    virtual ~A() = default; 

    // non-polymorphic interface in terms of private polymorphic 
    // implementation 

    int f() 
    try 
    { 
     // possibly lock a mutex here? 
     // possibly some setup code, logging, bookkeeping? 
     return f_impl(); 
    } 
    catch(const std::exception& e) { 
     // log the nested exception hierarchy 
     std::throw_with_nested(std::runtime_error(__func__)); 
    } 

private: 

    virtual int f_impl() = 0; 

}; 

Warum ist es wichtig, Ihrer Meinung nach einem Try-Catch-Block für f zu haben() - einpoklum vor 16 Minuten

@einpoklum Ich bin froh, dass Sie gefragt haben. Wenn Sie dies in jeder Methode und jeder Funktion tun und eine geschachtelte Ausnahme mit dem Funktionsnamen (und allen relevanten Argumenten) werfen, bedeutet dies, dass Sie alle verschachtelten Ausnahmen in Ihre Protokolldatei oder auspacken können, wenn Sie die Ausnahme schließlich abfangen cerr und du bekommst eine perfekte Stack-Trace, die genau auf das Problem hinweist.

Referenz verschachtelt Ausnahmen für auspacken:

http://en.cppreference.com/w/cpp/error/throw_with_nested

tut das nicht weh Leistung?

Kein einziges bisschen.

Aber es ist ein Schmerz, eine Funktion try-Block zu jeder Funktion

Es ist eine viel größere Schmerzen hinzufügen zu haben, um zu versuchen, ein Problem zu reproduzieren, dass Sie keine Ahnung, wie oder warum es aufgetreten ist, und keine Ahnung vom Kontext. Vertrauen Sie mir auf diese ...

+0

Warum ist es Ihrer Meinung nach wichtig, einen try-catch-Block für 'f()' zu haben? – einpoklum

+0

@einpoklum Ich bin froh, dass Sie gefragt haben. Wenn Sie dies in jeder Methode und jeder Funktion tun und eine geschachtelte Ausnahme mit dem Funktionsnamen (und allen relevanten Argumenten) werfen, bedeutet dies, dass Sie alle verschachtelten Ausnahmen in Ihre Protokolldatei oder auspacken können, wenn Sie die Ausnahme schließlich abfangen cerr und du bekommst eine perfekte Stack-Trace, die genau auf das Problem hinweist. –

+0

Sie haben den Grund für die Bevorzugung einer verschachtelten Ausnahme gegenüber einer einfachen Ausnahme erläutert. Aber warum überhaupt eine Ausnahme? Niemand sagte, dass f() notwendigerweise etwas werfen würde. Empfiehlst du den try-catch-Block in allen virtuellen Methoden in der Basisklasse/Mixin? – einpoklum

Verwandte Themen