2014-04-24 8 views
11

Za chwilę przeprowadzimy (w ciągu najbliższych dwóch lat) wszystkie nasze kompilatory do kompilatorów C++ 11-ready.Czy ten wzorzec jest prawidłowy dla migracji wstecznej z C++ 03 do klasy enum C++ 11?

Nasi klienci wykorzystają nasze nagłówki i jesteśmy teraz w stanie napisać (mniej lub bardziej od zera) nagłówki naszego nowego interfejsu API.

Musimy więc wybrać między utrzymaniem C++ 03 (z ich wszystkimi brodawkami) lub użyciem klasy zawijania do symulacji notacji C++ 11, ponieważ chcemy, na koniec, przenieść te enumy do C + +11.

Czy proponowany pomysł "LikeEnum" jest poniżej realnego rozwiązania, czy też kryją się za nim niespodziewane niespodzianki?

template<typename def, typename inner = typename def::type> 
class like_enum : public def 
{ 
    typedef inner type; 
    inner val; 

public: 

    like_enum() {} 
    like_enum(type v) : val(v) {} 
    operator type() const { return val; } 

    friend bool operator == (const like_enum & lhs, const like_enum & rhs) { return lhs.val == rhs.val; } 
    friend bool operator != (const like_enum & lhs, const like_enum & rhs) { return lhs.val != rhs.val; } 
    friend bool operator < (const like_enum & lhs, const like_enum & rhs) { return lhs.val < rhs.val; } 
    friend bool operator <= (const like_enum & lhs, const like_enum & rhs) { return lhs.val <= rhs.val; } 
    friend bool operator > (const like_enum & lhs, const like_enum & rhs) { return lhs.val > rhs.val; } 
    friend bool operator >= (const like_enum & lhs, const like_enum & rhs) { return lhs.val >= rhs.val; } 
}; 

co pozwoliłoby nam uaktualnić nasze teksty stałe bez konieczności niepożądanych zmian w kodzie użytkownika:

// our code (C++03)     |  our code C++11 
// --------------------------------------+--------------------------- 
             | 
struct KlingonType      | enum class Klingon 
{          | { 
    enum type        | Qapla, 
    {          | Ghobe, 
     Qapla,        | Highos 
     Ghobe,        | } ; 
     Highos        | 
    } ;         | 
} ;          | 
             | 
typedef like_enum<KlingonType> Klingon ; | 
             | 
// --------------------------------------+--------------------------- 
//    client code (both C++03 and C++11) 

void foo(Klingon e) 
{ 
    switch(e) 
    { 
     case Klingon::Qapla : /* etc. */ ; break ; 
     default :    /* etc. */ ; break ; 
    } 
} 

Uwaga: LikeEnum była inspirowana przez Type Safe Enum idiom

nocie 2 : Kompatybilność źródeł nie obejmuje błędu kompilacji z powodu niejawnej konwersji na int: są one uznawane za niepożądane, a klient zostanie powiadomiony w adv do bezpośredniej konwersji na liczbę całkowitą.

+0

Wykonaj dwa z 'wewnętrznego' funkcji 'szablonu', którą SFINAE zapewnia, że ​​żądany typ to' wewnętrzny', aby uniknąć jedno-pośrednich konwersji jednego użytkownika? Lub użyć typu pośredniego? Kod idola i problem w zasadzie ... (lub czy problem ten nie dotyczy 'enum's?) – Yakk

Odpowiedz

5

Krótka odpowiedź brzmi: tak, to realistyczne rozwiązanie (z jedną poprawką).

Oto długa odpowiedź. :)


Występuje błąd w czasie kompilacji z funkcjami porównania, ściśle mówiąc. Spowoduje to problemy z przenośnością w kompilatorach zgodnych ze standardami. W szczególności należy wziąć pod uwagę następujące elementy:

bool foo(Klingon e) { return e == Klingon::Qapla } 

Kompilator nie powinien wiedzieć, jakie przeciążenie operator== w użyciu, ponieważ zarówno konwersja e do KlingonType::type pośrednio (poprzez operator type() const) i konwersji Klingon::Qapla do Klingon pośrednio (poprzez Klingon(type)) potrzebny konwersja.

Wymaganie jako explicit naprawi ten błąd. Oczywiście explicit nie istnieje w C++ 03. Oznacza to, że musisz postąpić tak, jak sugeruje @Yakk w komentarzach i użyć czegoś podobnego do idiomu safe-bool dla typu inner. Całkowite usunięcie operator type() const nie jest opcją, ponieważ usunie jawne konwersje do typów integralnych.

Ponieważ mówisz, że masz wszystko w porządku, a niejawne konwersje są nadal możliwe, prostszym rozwiązaniem byłoby zdefiniowanie funkcji porównawczych z podstawowym typem enum.Więc oprócz:

friend bool operator == (const like_enum & lhs, const like_enum & rhs) { return lhs.val == rhs.val; } 
friend bool operator != (const like_enum & lhs, const like_enum & rhs) { return lhs.val != rhs.val; } 
friend bool operator < (const like_enum & lhs, const like_enum & rhs) { return lhs.val < rhs.val; } 
friend bool operator <= (const like_enum & lhs, const like_enum & rhs) { return lhs.val <= rhs.val; } 
friend bool operator > (const like_enum & lhs, const like_enum & rhs) { return lhs.val > rhs.val; } 
friend bool operator >= (const like_enum & lhs, const like_enum & rhs) { return lhs.val >= rhs.val; } 

musisz również:

friend bool operator ==(const like_enum& lhs, const type rhs) { return lhs.val == rhs; } 
friend bool operator !=(const like_enum& lhs, const type rhs) { return lhs.val != rhs; } 
friend bool operator < (const like_enum& lhs, const type rhs) { return lhs.val < rhs; } 
friend bool operator <=(const like_enum& lhs, const type rhs) { return lhs.val <= rhs; } 
friend bool operator > (const like_enum& lhs, const type rhs) { return lhs.val > rhs; } 
friend bool operator >=(const like_enum& lhs, const type rhs) { return lhs.val >= rhs; } 
friend bool operator ==(const type lhs, const like_enum& rhs) { return operator==(rhs, lhs); } 
friend bool operator !=(const type lhs, const like_enum& rhs) { return operator!=(rhs, lhs); } 
friend bool operator < (const type lhs, const like_enum& rhs) { return operator> (rhs, lhs); } 
friend bool operator <=(const type lhs, const like_enum& rhs) { return operator>=(rhs, lhs); } 
friend bool operator > (const type lhs, const like_enum& rhs) { return operator< (rhs, lhs); } 
friend bool operator >=(const type lhs, const like_enum& rhs) { return operator<=(rhs, lhs); } 

Po ustaleniu powyższego, nie ma zauważalnej różnicy semantycznie (pomijając niejawne konwersje są możliwe). Jedyną różnicą, jaką znalazłem, jest wartość std::is_pod<Klingon>::value z <type_traits> w C++ 11. W wersji C++ 03 będzie to false, natomiast przy użyciu enum class es będzie to true. W praktyce oznacza to (bez optymalizacji), że Klingon może być przenoszony w rejestrze, podczas gdy wersja like_enum musi znajdować się na stosie.

Ponieważ nie określisz podstawowej reprezentacji enum class, sizeof(Klingon) prawdopodobnie będzie taka sama dla obu, ale nie będę na niej polegać. Niewiarygodność podstawowej reprezentacji wybranej przez różne implementacje była w końcu częścią motywacji silnie typowanych enum s.

Here's proof z powyższych dwóch akapitów dla języka ++ 3.0+, g ++ 4.5+ i msvc 11+.


Teraz, jeśli chodzi o skompilowane dane wyjściowe, obie będą miały niekompatybilny ABI. Oznacza to, że cała twoja baza musi używać jednego lub drugiego. Nie będą się mieszać. Dla mojego systemu (clang ++ - 3.5 na OSX) powyższy znak funkcji to __Z1f9like_enumI11KlingonTypeNS0_4typeEE dla wersji C++ 03 i __Z1f7Klingon dla wersji C++ 11. To powinno być problemem tylko wtedy, gdy są one eksportowanymi funkcjami biblioteki.

Wyprowadzony zestaw jest identyczny w moich testach dla clang ++ i g ++ po włączeniu optymalizacji do -O2. Prawdopodobnie inne kompilatory optymalizujące będą mogły również rozwinąć Klingon do KlingonType::type. Bez optymalizacji wersja enum class nadal oczywiście uniknie wszystkich wywołań funkcji konstruktora i operatora porównania.

+0

Różnica ABI nie będzie problemem, ponieważ kierujemy się wyłącznie zgodnością wsteczną źródła: Klient ma dokonać rekompilacji za każdym razem, gdy aktualizujemy nagłówki, więc gdy tylko przejdziemy do C++ 11, klient również ... Jeśli chodzi o rozmiar bazowego typu, a nawet is_pod, nie będzie to również problemem. W końcu pozostaje tylko niewielki problem porównań ... I Wow ... Gdybym mógł dwa razy przemycić twoją odpowiedź, to bym ... :-) – paercebal

Powiązane problemy