Le enumerazioni C ++ sono firmate o non firmate?


107

Le enumerazioni C ++ sono firmate o non firmate? E per estensione è sicuro convalidare un input verificando che sia <= il tuo valore massimo e tralasciare> = il tuo valore minimo (supponendo che tu abbia iniziato da 0 e incrementato di 1)?


Quando usiamo un tipo enum in un contesto che richiede il segno di esso, stiamo effettivamente parlando di convertire implicitamente l'enum in un tipo integrale. Lo standard C ++ 03 dice che questo viene fatto da Integral Promotion, niente relativo al tipo sottostante di enum. Quindi, non capisco perché ogni risposta qui menziona il tipo sottostante non è definito dallo standard? Ho descritto la Comportamento atteso qui: stackoverflow.com/questions/24802322/...
Javaman

Risposte:


60

Non dovresti fare affidamento su alcuna rappresentazione specifica. Leggi il seguente link . Inoltre, lo standard afferma che è definito dall'implementazione quale tipo integrale viene utilizzato come tipo sottostante per un'enumerazione, tranne per il fatto che non deve essere maggiore di int, a meno che un valore non possa adattarsi a int o un int senza segno.

In breve: non puoi fare affidamento sul fatto che un'enumerazione sia firmata o non firmata.


28
La risposta di Michael Burr (che cita lo standard) in realtà implica che puoi fare affidamento sul fatto che sia firmato se definisci un valore enum come negativo perché il tipo è in grado di "rappresentare tutti i valori dell'enumeratore definiti nell'enumerazione".
Samuel Harmer

101

Andiamo alla fonte. Ecco cosa dice il documento dello standard C ++ 03 (ISO / IEC 14882: 2003) in 7.2-5 (Dichiarazioni di enumerazione):

Il tipo sottostante di un'enumerazione è un tipo integrale che può rappresentare tutti i valori dell'enumeratore definiti nell'enumerazione. È definito dall'implementazione quale tipo integrale viene utilizzato come tipo sottostante per un'enumerazione, tranne per il fatto che il tipo sottostante non deve essere maggiore di int a meno che il valore di un enumeratore non possa rientrare in un int o unsigned int.

In breve, il tuo compilatore può scegliere (ovviamente, se hai numeri negativi per alcuni dei tuoi valori di ennumerazione, sarà firmato).


Come possiamo evitare le supposizioni del compilatore e dirgli di usare un tipo senza segno sottostante quando tutti i valori di enumerazione sono piccoli interi positivi? (Stiamo rilevando un risultato di UBsan perché il compilatore sta scegliendo un int e gli int soffrono di overflow. Il valore è unsigned e positivo, e il nostro uso dipende dall'involucro senza segno per fornire un decremento o "passo negativo").
jww

@jww - dipenderà da quale compilatore stai usando esattamente, immagino. Poiché lo standard non determina il tipo sottostante e lo lascia all'implementazione, è necessario esaminare la documentazione del proprio strumento e vedere se questa opzione è possibile. Se vuoi garantire un certo comportamento nel tuo codice, perché non eseguire il cast del membro enum che usi nell'espressione?
ysap


15

Non dovresti fare affidamento sul fatto che sia firmato o non firmato. Secondo lo standard è definito dall'implementazione quale tipo integrale viene utilizzato come tipo sottostante per un'enumerazione. Nella maggior parte delle implementazioni, tuttavia, è un numero intero con segno.

In C ++ 0x verranno aggiunte enumerazioni fortemente tipizzate che ti permetteranno di specificare il tipo di enumerazione come:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

Anche ora, tuttavia, è possibile ottenere una semplice convalida utilizzando enum come variabile o tipo di parametro come questo:

enum Fruit { Apple, Banana };

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit
                    // even though it has the same value as banana.

Penso che il tuo secondo esempio sia un po 'confuso :)
Miral

5

Il compilatore può decidere se le enumerazioni sono firmate o non firmate.

Un altro metodo per convalidare le enumerazioni consiste nell'usare l'enumerazione stessa come tipo di variabile. Per esempio:

enum Fruit
{
    Apple = 0,
    Banana,
    Pineapple,
    Orange,
    Kumquat
};

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit even though it has the same value as banana.

5

Anche alcune vecchie risposte hanno ottenuto 44 voti positivi, tendo a non essere d'accordo con tutti loro. In breve, non credo che dovremmo preoccuparci underlying typedell'enumerazione.

Prima di tutto, il tipo Enum C ++ 03 è un tipo distinto a sé stante che non ha il concetto di segno. Dal momento che dallo standard C ++ 03dcl.enum

7.2 Enumeration declarations 
5 Each enumeration defines a type that is different from all other types....

Quindi, quando parliamo del segno di un tipo enum, diciamo quando confrontiamo 2 operandi enum usando l' <operatore, stiamo effettivamente parlando della conversione implicita del tipo enum in un tipo integrale. È il segno di questo tipo integrale che conta . E quando si converte enum in tipo integrale, si applica questa istruzione:

9 The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).

E, a quanto pare, il tipo sottostante dell'enumerazione non ha nulla a che fare con la Promozione Integrale. Poiché lo standard definisce la promozione integrale in questo modo:

4.5 Integral promotions conv.prom
.. An rvalue of an enumeration type (7.2) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration
(i.e. the values in the range bmin to bmax as described in 7.2: int, unsigned int, long, or unsigned long.

Quindi, se un tipo di enumerazione diventa signed into unsigned intdipende dal fatto che signed intpossa contenere tutti i valori degli enumeratori definiti, non il tipo sottostante dell'enumerazione.

Vedere la mia domanda correlata Segno del tipo di enumerazione C ++ non corretto dopo la conversione in tipo integrale


È importante quando compili con -Wsign-conversion. Lo usiamo per aiutare a rilevare errori non intenzionali nel nostro codice. Ma +1 per citare lo standard e sottolineare che un enum non ha alcun tipo ( signedcontro unsigned) ad esso associato.
jww

4

In futuro, con C ++ 0x, saranno disponibili enumerazioni fortemente tipizzate e avranno diversi vantaggi (come l'indipendenza dai tipi, i tipi sottostanti espliciti o l'ambito esplicito). Con ciò potresti essere più sicuro del segno del tipo.


4

Oltre a ciò che altri hanno già detto su firmato / non firmato, ecco cosa dice lo standard sull'intervallo di un tipo enumerato:

7.2 (6): "Per un'enumerazione in cui e (min) è l'enumeratore più piccolo ed e (max) è il più grande, i valori dell'enumerazione sono i valori del tipo sottostante nell'intervallo da b (min) a b (max ), dove b (min) eb (max) sono, rispettivamente, i valori più piccolo e più grande del campo di bit più piccolo che può memorizzare e (min) ed e (max). È possibile definire un'enumerazione che ha valori non definiti da uno qualsiasi dei suoi enumeratori. "

Quindi per esempio:

enum { A = 1, B = 4};

definisce un tipo enumerato dove e (min) è 1 ed e (max) è 4. Se il tipo sottostante è firmato int, il campo di bit richiesto più piccolo ha 4 bit e se gli int nella tua implementazione sono complemento a due, l'intervallo valido di l'enumerazione va da -8 a 7. Se il tipo sottostante non è firmato, ha 3 bit e l'intervallo è compreso tra 0 e 7. Se ti interessa, controlla la documentazione del compilatore (ad esempio se desideri eseguire il cast di valori integrali diversi dagli enumeratori tipo enumerato, quindi è necessario sapere se il valore è compreso nell'intervallo dell'enumerazione o meno, in caso contrario il valore dell'enumerazione risultante non è specificato).

Se questi valori sono un input valido per la tua funzione potrebbe essere un problema diverso dal fatto che siano valori validi del tipo enumerato. Il tuo codice di controllo è probabilmente preoccupato per il primo piuttosto che per il secondo, quindi in questo esempio dovresti almeno controllare> = A e <= B.


0

Controllalo con std::is_signed<std::underlying_type+ enumerazioni con ambito predefinito suint

https://en.cppreference.com/w/cpp/language/enum implica:

main.cpp

#include <cassert>
#include <iostream>
#include <type_traits>

enum Unscoped {};
enum class ScopedDefault {};
enum class ScopedExplicit : long {};

int main() {
    // Implementation defined, let's find out.
    std::cout << std::is_signed<std::underlying_type<Unscoped>>() << std::endl;

    // Guaranteed. Scoped defaults to int.
    assert((std::is_same<std::underlying_type<ScopedDefault>::type, int>()));

    // Guaranteed. We set it ourselves.
    assert((std::is_same<std::underlying_type<ScopedExplicit>::type, long>()));
}

GitHub a monte .

Compila ed esegui:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main

Produzione:

0

Testato su Ubuntu 16.04, GCC 6.4.0.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.