2014-04-24 6 views
11

Wir sind dabei, alle unsere Compiler (in den nächsten zwei Jahren) auf C++ 11-fähige Compiler umzustellen.Ist dieses Muster für eine rückwärtskompatible Quellmigration von C++ 03 enum in C++ 11 enum-Klasse in Ordnung?

Unsere Kunden werden unsere Header verwenden, und wir sind jetzt in der Lage, Header für unsere neue API (mehr oder weniger von Grund auf) zu schreiben.

Also müssen wir zwischen C++ 03 enums (mit all ihren Warzen) wählen oder eine Wrapping-Klasse verwenden, um die C++ 11-Notation zu simulieren, weil wir diese Enums schließlich nach C + verschieben wollen +11.

Ist das "LikeEnum" -Idiom unten eine brauchbare Lösung, oder verbergen sich unerwartete Überraschungen dahinter?

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

die es uns ermöglichen, unsere Aufzählungen zu aktualisieren, ohne unerwünschte Änderungen in dem Benutzercode benötigt:

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

Hinweis: Die LikeEnum wurde von der Note Type Safe Enum idiom

inspiriert 2 : Quellkompatibilität deckt Kompilierungsfehler wegen impliziter Konvertierung zu int nicht ab: Diese werden als unerwünscht betrachtet und der Client wird in adv benachrichtigt um die Ganzzahlkonvertierung explizit zu machen.

+0

Machen Sie zwei von 'inner 'eine' template' Funktion, die SFINAE stellt sicher, dass der angeforderte Typ' inner 'ist, um Ein-Benutzer-implizite Konvertierungen zu vermeiden? Oder verwenden Sie einen Zwischentyp? Das Safe-Bool-Idiom und das Problem im Grunde ... (oder würde dieses Problem nicht für 'Enum's gelten?) – Yakk

Antwort

5

Die kurze Antwort ist ja, das ist eine praktikable Lösung (mit einem Fix).

Hier ist die lange Antwort. :)


Sie haben einen Kompilierungsfehler mit Ihren Vergleichsfunktionen, streng genommen. Dies führt zu Portabilitätsproblemen mit standardkonformen Compilern. Insbesondere ist Folgendes zu beachten:

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

Der Compiler nicht die Überlastung von operator== zu verwenden, da beide Umwandlung e-KlingonType::type implizit (über operator type() const) und Umwandlung Klingon::Qapla zu Klingon implizit (über Klingon(type)) benötigt man wissen sollte Umwandlung.

Erforderlich operator type() const zu explicit wird diesen Fehler beheben. Natürlich existiert explicit nicht in C++ 03. Das bedeutet, dass Sie tun müssen, wie @Yakk in den Kommentaren vorschlägt, und etwas Ähnliches wie das Safe-Bool-Idiom für den Typ inner verwenden. Das Entfernen von operator type() const vollständig ist keine Option, da explizite Konvertierungen zu integralen Typen entfernt werden.

Da Sie sagen, dass Sie mit impliziten Konvertierungen noch immer möglich sind, wäre es einfacher, Vergleichsfunktionen mit dem zugrunde liegenden enum-Typ zu definieren.Also zusätzlich zu:

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

werden Sie auch brauchen:

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

Nach der obigen Festsetzung wenig spürbaren Unterschied ist semantisch (ohne Berücksichtigung von impliziten Konvertierungen sind möglich). Der einzige Unterschied, den ich gefunden habe, ist der Wert std::is_pod<Klingon>::value von <type_traits> in C++ 11. Bei Verwendung der Version C++ 03 wird dies false sein, während enum class es true ist. In der Praxis bedeutet dies (ohne Optimierung), dass eine Klingon mit enum class in einem Register herumgetragen werden kann, während die like_enum Version auf dem Stapel sein muss.

Da Sie die zugrunde liegende Darstellung des enum class nicht angeben, wird sizeof(Klingon) wahrscheinlich für beide gleich sein, aber ich würde nicht darauf verlassen. Die Unzuverlässigkeit der zugrundeliegenden Repräsentation, die von verschiedenen Implementierungen gewählt wurde, war Teil der Motivation hinter stark typisierten enum s schließlich.

Here's proof der obigen zwei Absätze für Clang ++ 3.0+, g ++ 4.5+ und msvc 11+.


Jetzt in Bezug auf die kompilierte Ausgabe haben beide offensichtlich inkompatible ABI. Dies bedeutet, dass Ihre gesamte Codebasis entweder das eine oder das andere verwenden muss. Sie werden sich nicht vermischen. Für mein System (clang ++ - 3.5 auf OSX) ist das obige Funktionssymbol __Z1f9like_enumI11KlingonTypeNS0_4typeEE für die C++ 03-Version und __Z1f7Klingon für die C++ 11-Version. Dies sollte nur ein Problem sein, wenn es sich um exportierte Bibliotheksfunktionen handelt.

Die ausgegebene Assembly ist identisch in meinem Test für clang ++ und g ++ nach Drehen Optimierungen zu -O2. Vermutlich können auch andere optimierende Compiler Klingon bis KlingonType::type auspacken. Ohne Optimierungen vermeidet die enum class Version natürlich alle Konstruktor- und Vergleichsoperatorfunktionsaufrufe.

+0

Der ABI-Unterschied wird kein Problem sein, da wir nur die Quell-Rückwärtskompatibilität zielen: Der Client wird voraussichtlich neu kompilieren Jedes Mal, wenn wir die Header aktualisieren, wird der Client in dem Moment, in dem wir nach C++ 11 wechseln, ebenfalls ... Wie bei der Größe des zugrunde liegenden Typs oder sogar beim is_pod ist dies ebenfalls kein Problem. Am Ende bleibt nur das kleine Problem der Vergleiche ... Und Wow ... Wenn ich deine Antwort zweimal upmodieren könnte, würde ich ... :-) – paercebal