2013-05-09 10 views
8

Wir haben einen speziellen Rahmen für die Schnittstellen in unserem Projekt, und einen Teil der Voraussetzungen ist, dass Klassen, die eine Schnittstelle stellen nur als virtuelle Basisklassen verwendet werden können, nicht als nicht-virtuelle. Gibt es eine Möglichkeit, dies in Code zu erzwingen? Das heißt, einen Kompilierungsfehler erzeugen, wenn die Klasse von nicht virtuell abgeleitet wird.Kraft aus einer Klasse ableiten praktisch

Ich habe Zugriff auf C++ 11 wie von VS 2010 implementiert: das bedeutet static_assert, enable_if und <type_traits> sind verfügbar.

+0

Es gibt möglicherweise bessere Möglichkeiten, dies jetzt zu tun, aber in VS2010 gibt es ein Microsoft-spezifisches Schlüsselwort namens "__interface". Dies ist jedoch a) nicht portabel und b) Sie müssen vorsichtig sein, da diese Schnittstellen aus irgendeinem Grund keinen virtuellen Destruktor haben dürfen. – Excelcius

+1

Das klingt eher nach einer technischen Spezifikation als nach einer funktionalen Anforderung. Warum brauchst du das? –

+0

@Excelcius Wir entwickeln sowohl für Linux als auch für Windows; Ich habe VS2010 aufgelistet, weil gcc in C++ 11 Unterstützung voraus ist. – Angew

Antwort

3

IMO gibt es keine saubere und plattformunabhängige Lösung für dieses Problem zur Verfügung.

Der beste Weg ist, von Hand gehen und jedes einzelne Erbe virtual Erbe zu ändern.
Um das zu erreichen, um die abgeleiteten Klassen Ihrer Schnittstelle zu identifizieren (zB class Base) ist leicht (!). Im Folgenden Schritte, die befolgt werden können:

  1. Make class Base als final (C++ 11); class Base final { ... dh
  2. Kompilieren Sie den Code, wird es Compiler-Fehler für alle seine abgeleiteten Klassen
  3. Go und überprüfen jede abgeleitete Klasse und machen das Erbe als virtual
  4. Entfernen Sie die final Schlüsselwort und kompilieren den Code erfolgreich
  5. erzeugen

Dieser Prozess (leider) hat in regelmäßigen Abständen folgen, wann immer Sie eine solche geistige Gesundheit Überprüfung tun wollen.

1

interessantes Problem. Sie können sich dem gewünschten Ziel nähern, indem Sie die Interface-Klasse verbergen und eine konkrete Klasse offenlegen, die virtuell von der Schnittstelle erbt. Dies bringt natürlich einige Umwege und Unannehmlichkeiten mit sich, aber es kann an Ihre Bedürfnisse angepasst werden. Hier ein Beispiel:

#include <iostream> 
using namespace std; 

class Hide { 
    struct VInterface { 
     void foo() const { cout << "VInterface::foo()\n"; } 
     VInterface const &as_interface() const { return *this; } 
    protected: 
     virtual ~VInterface() { } 
    }; 
public: 
    struct VBase : virtual VInterface { 
    }; 
}; 
typedef Hide::VBase VBase; 
struct VDiamond1 : VBase { }; 
struct VDiamond2 : VBase { }; 
struct VConcrete : VDiamond1, VDiamond2 { }; 

int main() { 
    VConcrete vc; 
    auto const &vi = vc.as_interface(); 
    vi.foo(); 
} 

Es kann möglich sein, einen Namen mit decltype() und as_interface() rekonstruieren, die für die Vererbung verwendbar sein können, aber die, die ich versuchte, führten zu Compiler-Fehlern, dass der destructor wurde geschützt, so erwarte ich, dass, wenn Es ist möglich, es ist zumindest relativ schwierig und könnte für Ihre Bedürfnisse ausreichen.

3

Dies kann zum Zeitpunkt der Kompilierung überprüft werden. Der Schlüssel ist, dass, wenn wir ein Diamantmuster haben:

diamond

Sie eindeutig D&-A& werfen können. Wenn jedoch die Vererbung ist nicht virtuell:

not diamond

die Besetzung wäre mehrdeutig. Also lass uns versuchen, einen Diamanten zu machen!

template <typename Base, typename Derived> 
class make_diamond { 
    struct D2 : virtual Base { }; // this one MUST be virtual 
            // otherwise we'd NEVER have a diamond 
public: 
    struct type : Derived, D2 { }; 
}; 

an welcher Stelle es ist nur ein weiteres void_t -Stil Typ Merkmal:

template <typename Base, typename Derived, typename = void> 
struct is_virtual_base_of : std::false_type { }; 

template <typename Base, typename Derived> 
struct is_virtual_base_of<Base, Derived, void_t< 
    decltype(static_cast<Base&>(
     std::declval<typename make_diamond<Base, Derived>::type&>())) 
    >> : std::true_type { }; 

Wenn die Umwandlung eindeutig ist, wird der Ausdruck in der teilweisen Spezialisierung gültig und dass Spezialisierung bevorzugt. Wenn der Cast zweideutig ist, haben wir einen Substitutionsfehler und landen am Primary. Beachten Sie, dass Base hier nicht eigentlich keine virtual Member-Funktionen haben müssen:

struct A { }; 
struct B : public A { }; 
struct C : virtual A { }; 

std::cout << is_virtual_base_of<A, B>::value << std::endl; // 0 
std::cout << is_virtual_base_of<A, C>::value << std::endl; // 1 

Und wenn es keine rein virtuelle Member-Funktionen hat, wir haben sie nicht außer Kraft zu setzen, da wir eigentlich nie ein Objekt konstruieren .

struct A2 { virtual void foo() = 0; }; 
struct B2 : public A2 { void foo() override { } }; 
struct C2 : virtual A2 { void foo() override { } }; 

std::cout << is_virtual_base_of<A2, B2>::value << std::endl; // 0 
std::cout << is_virtual_base_of<A2, C2>::value << std::endl; // 1 

Natürlich, wenn Ihre Klasse final markiert ist, wird dies nicht funktionieren. Aber dann, wenn es final wäre, wäre es egal, welche Art von Vererbung es ohnehin hatte.

+0

Ich kenne einfachere Ansätze, um zu überprüfen, ob eine Basisklasse virtuell ist oder nicht (z. B. Casting, Pointer-zu-Member-Konvertierungen usw.), aber diese sind von der Verfügbarkeit der Basisklasse betroffen. Ihr Ansatz, der die Mehrdeutigkeitsprüfung verwendet, wird nicht vom Zugriffsschutz beeinflusst, also +1.Es wird jedoch durch bereits bestehende Mehrdeutigkeiten, z.B. 'struct D: B, C {};' Also 'is_virtual_base_of' ist etwas wie' is_nonfinal_unambiguous_virtual_base_of';) – dyp

+0

@dyp Nicht sicher, was du mit bestehenden Unklarheiten meinst? – Barry

+0

'D d; A & a = d; 'ist mehrdeutig: http://coliru.stacked-crooked.com/a/d92de17312866f0b – dyp

Verwandte Themen