2013-05-09 5 views
8

Mamy specjalne ramy dla interfejsów w naszym projekcie, a część wymagań jest taka, że ​​klasy reprezentujące interfejs mogą być używane tylko jako wirtualne klasy bazowe, a nie jako obiekty nie wirtualne. Czy istnieje sposób na wymuszenie tego w kodzie? Oznacza to, że generuje błąd kompilacji, jeśli klasa pochodzi z niecałkowicie.Siła wywodząca się z klasy wirtualnie

Mam dostęp do C++ 11 wdrożonego przez VS 2010: oznacza to, że dostępne są static_assert, enable_if i <type_traits>.

+0

Możliwe, że można to teraz zrobić lepiej, ale w VS2010 istnieje słowo kluczowe o nazwie "__interface" firmy Microsoft. Jednak jest to a) nie jest przenośne i b) Musisz być ostrożny, ponieważ z jakiegoś powodu te interfejsy nie mogą mieć wirtualnego destruktora. – Excelcius

+1

To brzmi bardziej jak specyfikacja techniczna niż wymaganie funkcjonalne. Dlaczego tego potrzebujesz? –

+0

@Excelcius Pracujemy dla systemu Linux i Windows; Wymieniłem VS2010, ponieważ gcc wyprzedza wsparcie C++ 11. – Angew

Odpowiedz

3

IMO, dla tego problemu nie ma czystego i niezależnego od platformy rozwiązania.

Najlepszym sposobem jest ręczne przejście i zmianę każdego dziedziczenia na dziedziczenie virtual.
Aby to osiągnąć, zidentyfikowanie klas pochodnych interfejsu (np. class Base) jest łatwe (!). Poniżej można wykonać następujące kroki:

  1. Ustaw jako class Base jako final (C++ 11); tj class Base final { ...
  2. kompilacji kodu wygeneruje błąd kompilatora dla wszystkich swoich klas pochodnych
  3. idź i sprawdź każdą klasę pochodną i dokonać dziedziczenia virtual
  4. Usuń słowa kluczowego final i skompilować kod powodzeniem

Ten proces (niestety) musi być przeprowadzany okresowo, kiedy tylko chcesz wykonać takie sprawdzenie poprawności.

1

Interesujący problem. Możesz być w stanie zbliżyć się do tego, co chcesz, ukrywając klasę interfejsu i eksponując konkretną klasę wirtualnie dziedziczącą po interfejsie. To oczywiście pociąga za sobą pewne obejścia i niezręczność, ale może być dostosowane do twoich potrzeb. Oto przykład:

#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(); 
} 

może być możliwe do zrekonstruowania nazwę za decltype() i as_interface(), które mogą być wykorzystywane do dziedziczenia, ale te próbowałem powodowało błędy kompilatora, że ​​destructor został zabezpieczony, więc spodziewam się, że jeśli Jest to możliwe, przynajmniej stosunkowo trudne i może być wystarczające dla twoich potrzeb.

3

Jest to możliwe do sprawdzenia w czasie kompilacji. Kluczem jest to, że jeśli mamy wzór diamentowe:

diamond

można jednoznacznie oddania D& do A&. Jednakże, jeśli nie jest dziedziczenie wirtualne:

not diamond

obsada będzie niejednoznaczna. Więc spróbujmy zrobić diament!

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

W tym momencie jest to po prostu kolejna cecha void_t typ -Style:

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

Jeśli obsada jest jednoznaczne wyrażenie w częściowej specjalizacji będzie ważna i że specjalizacja będzie korzystny. Jeśli obsada jest niejednoznaczna, wystąpi awaria substytucji, a kończy się podstawową. Zauważ, że Base tutaj faktycznie nie trzeba mieć żadnych funkcji virtual członków:

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 

A jeśli ma żadnych czystych funkcji składowych wirtualnych, nie trzeba ich zastąpić ponieważ nigdy nie faktycznie budowy obiektu .

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 

Oczywiście jeśli klasa jest oznaczony final, to nie będzie działać w ogóle. Ale wtedy, gdyby było to final, nie miałoby znaczenia, jaki rodzaj dziedzictwa miał w ogóle.

+0

Znam prostsze metody sprawdzania, czy klasa podstawowa jest wirtualna (np. Odlewanie, konwersja po sygnale na członka itd.), Ale mają na nią wpływ dostępność klasy bazowej. Twoje podejście do sprawdzania niejednoznaczności nie ma wpływu na ochronę dostępu, więc +1.Jednak wpływ na nią mają już istniejące niejasności, np. 'struct D: B, C {};' Więc 'jest_wirtualna_nazwa' jest czymś w rodzaju 'is_nonfinal_unambiguous_virtual_base_of';) – dyp

+0

@dyp Nie wiesz, co masz na myśli przez istniejące niejasności? – Barry

+0

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