2013-07-13 12 views
20

Czy istnieje sposób określenia domyślnego konstruktora modelu enum class?User Defined C++ 11 klasa enum Domyślny konstruktor

Używam enum class do określenia zestawu wartości, które są dozwolone dla określonego typu danych w bibliotece: w tym przypadku jest to numer identyfikacyjny PIN GPIO Raspberry Pi. Wygląda to mniej więcej tak:

enum class PinID : int {N4 = 4, N17 = 17, /* ...etc... */ }

Chodzi o mnie w ten sposób, a nie tylko z użyciem, powiedzmy, int jest zapewnienie, że kod jest bezpieczny: Mogę static_assert (lub w inny sposób zapewnić w czasie kompilacji - rzeczywista zastosowana metoda nie jest dla mnie ważna) takie rzeczy, że ktoś nie popełnił błędu w pisowni (podanie 5 zamiast 4 itd.) i otrzymuję automatyczne komunikaty o błędach dla niedopasowania typów, itp.

The Problem polega na tym, że enum class ma domyślny konstruktor, który - ze względu na kompatybilność z C enum s, zakładam (skoro mają to samo zachowanie) - inicjuje na enum class odpowiednik 0. W tym przypadku nie ma wartości 0. Oznacza to, że użytkownik składając oświadczenie/definicja jak:

PinID pid = PinID();

jest coraz wyliczający, które nie są wyraźnie określone (i nawet nie wydają się „istnieć”, gdy patrzy się na kodzie), i może prowadzić do błędów w czasie wykonywania. Oznacza to również, że techniki takie jak switch nad wartościami jawnie zdefiniowanych enumeratorów są niemożliwe bez błędu/domyślnego przypadku - czegoś, czego chcę uniknąć, ponieważ zmusza mnie to do albo throw lub coś w stylu zwracają boost::optional, które są mniej podatny na analizę statyczną.

Próbowałem zdefiniować domyślny konstruktor bez skutku. Ja (rozpaczliwie) próbowałem zdefiniować funkcję, która ma nazwę enum class, ale to (raczej nieoczekiwanie) doprowadziło do dziwnych błędów kompilatora. Chcę zachować zdolność do rzutowania enum class do int, z wszystkimi wylicznikami N# odwzorowanymi na ich odpowiednie #, więc samo "zdefiniowanie", powiedzmy, N4 = 0 jest niedopuszczalne; to jest dla prostoty i zdrowego rozsądku.

Podejrzewam, że moje pytanie jest dwojakie: czy istnieje sposób na uzyskanie statycznego bezpieczeństwa, którego używam po enum class? Jeśli nie, jakie inne możliwości wolałbyś? Co chcę jest coś czego:

  1. jest domyślnym constructable
  2. mogą być wykonane do domyślnych skonstruować do dowolnej ważnej wartości
  3. zapewnia „skończony zbiór określonych” wartości zapewnianej przez enum class es
  4. jest co najmniej tak bezpieczne, jak typu AN enum class
  5. (korzystnie) nie obejmują polimorfizm wykonania

Powodem, dla którego chcę mieć domyślną łatwość konstrukcji, jest to, że zamierzam użyć boost::lexical_cast, aby zmniejszyć obciążenie składniowe związane z konwersjami między wartościami enum class, a rzeczywistymi powiązanymi string s, które wysyłam do systemu operacyjnego (w tym przypadku sysfs); boost::lexical_cast wymaga domyślnej możliwości konstruowania.

Błędy w moim uzasadnieniu są mile widziane - zaczynam podejrzewać, że enum class es są właściwym obiektem dla niewłaściwej pracy, w tym przypadku; wyjaśnienie będzie oferowane, jeśli zostaniesz o to poproszony. Dziękuję za Twój czas.

Odpowiedz

12

Typ zdefiniowany za pomocą lub enum struct nie jest klasą, ale wyliczeniem o określonym zakresie i nie może mieć zdefiniowanego domyślnego konstruktora. Standard C++ 11 definiuje, że twoja instrukcja PinID pid = PinID(); da zerową inicjalizację. Gdzie PinID został zdefiniowany jako enum class. Pozwala także ogólnie na typy wyliczeniowe, które przechowują wartości inne niż stałe modułu wyliczającego.

rozumieć, że PinID() daje zero inicjalizacji do przeczytania standardowe sekcje 3.9.9, 8.5.5, 8.5.7 i 8.5.10 razem:

8.5.10 - An object whose initializer is an empty set of parentheses, i.e.,(), shall be value-initialized

8.5.7 - To value-initialize an object of type T means: ... otherwise, the object is zero-initialized.

8.5.5 - To zero-initialize an object or reference of type T means: — if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;

3.9.9 - oświadcza, że ​​typy wyliczeniowe są częścią zestawu typów znanych jako skalarnych typów.

Możliwym rozwiązaniem:

aby spełnić Twoje punkty 1 do 5 można napisać klasę wzdłuż linii:

class PinID 
{ 
private: 
    PinID(int val) 
    : m_value(val) 
    {} 

    int m_value; 

public: 
    static const PinID N4; 
    static const PinID N17; 
    /* ...etc... */ 

    PinID() 
    : m_value(N4.getValue()) 
    {} 

    PinID(const PinID &id) 
    : m_value(id.getValue()) 
    {} 

    PinID &operator = (const PinID &rhs) 
    { 
     m_value = rhs.getValue(); 
     return *this; 
    } 

    int getValue() const 
    { 
     return m_value; 
    } 

    // Attempts to create from int and throw on failure. 
    static PinID createFromInt(int i); 

    friend std::istream& operator>>(std::istream &is, PinID &v) 
    { 
     int candidateVal(0); 
     is >> candidateVal; 
     v = PinID::createFromInt(candidateVal); 
     return is; 
    } 
}; 

const PinID PinID::N4 = PinID(4); 
/* ...etc... */ 

To może dać ci coś, co trzeba zrobić specyficzny wysiłki zmierzające do uzyskania nieprawidłowych wartości. Domyślny konstruktor i operator strumienia powinny zezwolić mu na pracę z leksykalną.

Wygląda na to, jak ważne są operacje na identyfikatorze PinID po jego utworzeniu, czy warto pisać klasy lub po prostu obsługiwać nieprawidłowe wartości wszędzie, gdy używana jest wartość.

+1

Patrząc na to, wygląda w zasadzie dobrze, ale czy to zadziała? Masz definicję klasy, która zawiera instancję jako członka ... ale są statyczne. Kompilator nie wkurza się wtedy. Sprytny niedźwiedź. – FizzixNerd

+0

@FizzixNerd Nie uruchomiłem go przez kompilator, ale powinien działać ... Myślę, że jeśli dodasz operatorów strumieniowych, powinieneś być w stanie użyć go bezpośrednio z lexical_cast, przypuszczam, że zgłoszę wyjątek, jeśli pojawi się niepoprawna wartość. – PeterSW

+0

@PeterSW, czy mógłbyś wskazać, które elementy w standardzie C++ 11 dotyczą twojej inicjalizacji zerowej? –

4

An enum class jest po prostu mocno wpisanym enum; to nie jest class. C++ 11 ponownie użył istniejącego słowa kluczowego class, aby uniknąć wprowadzania nowego słowa kluczowego, które złamałoby zgodność ze starszym kodem C++.

Jeśli chodzi o twoje pytanie, nie ma sposobu, aby zapewnić w czasie kompilacji, że obsada wymaga odpowiedniego kandydata. Rozważ:

int x; 
std::cin >> x; 
auto p = static_cast<PinID>(x); 

Jest to całkowicie legalne i nie ma możliwości statycznego upewnienia się, że użytkownik konsoli zrobił dobrze.

Zamiast tego należy sprawdzić w środowisku wykonawczym, czy wartość jest poprawna. Aby obejść to w sposób zautomatyzowany, jeden z moich współpracowników utworzył generator enum, który buduje te sprawdzenia i inne przydatne procedury, z uwzględnieniem pliku z wartościami wyliczeniowymi. Musisz znaleźć rozwiązanie, które będzie dla Ciebie skuteczne.

+0

Dzięki za odpowiedź! Mam świadomość, że klasa wyliczeniowa nie jest klasą, ale nadal ma domyślny konstruktor. Jeśli chodzi o sprawdzanie statyczne, oczywiście wiem, że nie mogę statycznie sprawdzać konwersji z ciągu-> enum - dla tego używam boost :: optioanls, ale mogę _stabilnie sprawdzić przynajmniej _jeden_ z enum- > ciąg, który obejmuje constexpr, prawda? – FizzixNerd

+3

@FizzixNerd "ma domyślny konstruktor" - Technicznie, nie. Ma zdefiniowane zachowanie dla inicjowania wartości.Inicjalizacja wartości obiektu typu klasy wywołuje domyślny konstruktor; inicjalizacja wartości obiektu typu numerycznego lub wyliczeniowego ustawia go na zero; wartość inicjalizacji obiektu typu wskaźnika ustawia go na wartość wskaźnika pustego. – aschepler

1

Wiem, że to pytanie jest przestarzały i że ma już zaakceptowanej odpowiedzi, ale tutaj jest techniką, która może pomóc w takiej sytuacji z niektórych nowszych funkcji C++

Można zadeklarować zmienną tej klasie za albo non static lub static, można to zrobić na kilka sposobów dozwolonych przy obsłudze twojego obecnego kompilatora.


dla Static:

#include <iostream> 
#include <array> 

template<unsigned... IDs> 
class PinIDs { 
private: 
    const std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public: 
    PinIDs() = default; 
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

Static:- Istnieją 3 sposoby, aby napisać to: (pierwszy z nich - C++ 11 lub 14 lub wyższe) ostatnie 2 (C++ 17).

Nie cytuj mnie w części C++ 11; Nie jestem do końca pewien, kiedy najpierw wprowadzono szablony variadic lub pakiety parametrów.

template<unsigned... IDs> 
class PinIDs{ 
private:   
    static const std::array<unsigned, sizeof...(IDs)> ids; 
public:  
    PinIDs() = default;  
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

template<unsigned... IDs> 
const std::array<unsigned, sizeof...(IDs)> PinIDs<IDs...>::ids { IDs... }; 

template<unsigned... IDs> 
class PinIDs{ 
private: 
    static constexpr std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public: 
    PinIDs() = default;  
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

template<unsigned... IDs> 
class PinIDs{ 
private: 
    static inline const std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public:  
    PinIDs() = default;  
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

Wszystkie powyższe przykłady albo nie statycznego lub statycznego z tych samych przypadku użycia poniżej i zapewnić właściwe wyniki:

int main() { 
    PinIDs<4, 17, 19> myId; 

    std::cout << myId[0] << " "; 
    std::cout << myId[1] << " "; 
    std::cout << myId[2] << " "; 

    std::cout << "\nPress any key and enter to quit." << std::endl; 
    char c; 
    std::cin >> c; 

    return 0; 
} 

Wyjście

4 17 19 
Press any key and enter to quit. 

Z tego typu klasy szablonu z wykorzystaniem zmiennej liczbie argumentów listę parametrów, nie trzeba używać żadnych konstruktora ale domyślne. Dodałem sprawdzanie granic do tablicy, aby operator[] nie przekroczył granic tego rozmiaru; Mógłbym rzucić błąd, ale z typem unsigned po prostu zwróciłem -1 jako niepoprawną wartość.

W tym typie nie ma wartości domyślnych, ponieważ trzeba utworzyć instancję tego rodzaju obiektów za pomocą listy parametrów szablonu z pojedynczym zestawem wartości. Jeśli chce się, mogą specialize this class z jednym parametrem 0 dla domyślnego typu. Kiedy tworzysz ten typ obiektu; jest ostateczny, ponieważ nie można go zmienić z deklaracji. Jest to obiekt stały, który nadal może być domyślnie skonstruowany.

Powiązane problemy