2013-02-11 13 views
7

私は1 enum以上をお持ちの場合は、例えば:Cの型保証された列挙型?

enum Greetings{ hello, bye, how }; 

enum Testing { one, two, three }; 

どのように私は正しいenumの使用を強制することができますか? たとえば、デバッグと読みやすくするために、oneを使用する必要がある場合、誰かがhelloを使用することを望ましくありません。

答えて

9

Cでは、ボイラープレートコードで偽装することができます。

typedef enum { HELLO_E, GOODBYE_E } greetings_t; 
struct greetings { greetings_t greetings; }; 
#define HELLO ((struct greetings){HELLO_E}) 
#define GOODBYE ((struct greetings){GOODBYE_E}) 

typedef enum { ONE_E, TWO_E } number_t; 
struct number { number_t number; }; 
#define ONE ((struct number){ONE_E}) 
#define TWO ((struct number){TWO_E}) 

void takes_greeting(struct greetings g); 
void takes_number(struct number n); 

void test() 
{ 
    takes_greeting(HELLO); 
    takes_number(ONE); 

    takes_greeting(TWO); 
    takes_number(GOODBYE); 
} 

これは、任意のオーバーヘッドが発生し、エラー代わりに警告を生成しないでください。私はGNU拡張を使用していないよ、とスプリアス警告が生成されないことを

 
$ gcc -c -std=c99 -Wall -Wextra test2.c 
test2.c: In function ‘test’: 
test2.c:19: error: incompatible type for argument 1 of ‘takes_greeting’ 
test2.c:20: error: incompatible type for argument 1 of ‘takes_number’ 

お知らせ。エラーのみ。また、私はこれはC99の複合リテラルをサポートする任意のコンパイラで動作するはず、

 
$ gcc --version 
powerpc-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5493) 
Copyright (C) 2005 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

を汚れと同じくらい古いですGCCのバージョンを使用していますことに注意してください。

+1

これはきちんとしていますが、switch文のenumを少し醜いものにしてしまいます。 – Toby

-2

enumを型定義し、その型の変数と関数の引数を宣言できます。

+0

を無用だgccのでは。 – LtWorf

8

Clangは、次の警告を表示します(ユーザーは警告をエラーにアップグレードできますが)。

enum Greetings { hello, bye, how }; 
enum Count { one, two, three }; 

void takes_greeting(enum Greetings x) {} 
void takes_count(enum Count x) {} 

int main() { 
    takes_greeting(one); 
    takes_count(hello); 
} 

コンパイラ出力:

cc  foo.c -o foo 
foo.c:8:17: warning: implicit conversion from enumeration type 'enum Count' to different enumeration type 'enum Greetings' [-Wenum-conversion] 
     takes_greeting(one); 
     ~~~~~~~~~~~~~~ ^~~ 
foo.c:9:14: warning: implicit conversion from enumeration type 'enum Greetings' to different enumeration type 'enum Count' [-Wenum-conversion] 
     takes_count(hello); 
     ~~~~~~~~~~~ ^~~~~ 

ユーザーは、コンパイラからエラーや警告を無視しようとしている場合は、あなたがそれらを助けるために行うことができますあまりありません。

+2

しかし悲しいことに、gccはCモードでも同様の警告を出さない。私はmsvcがそうするかどうかわからない。 gccをどのように欺くかについてのいくつかのコメントがあります(http://stackoverflow.com/questions/4669454/how-to-make-gcc-warn-about-passing-wrong-enum-to-a-function)。読みやすさを犠牲にして警告を出す。 –

2

これはあなたが聞きたくない回答です。 Cでは、あなたは本当にできません。あなたのCコードがC++の "Clean C"サブセットにある場合は、C++コンパイラでコンパイルして間違ったenum/int値などのエラーをすべて得ることができます。

+0

C++の場合でも、単純ではありません.C++ 11の "enum class"機能が必要です。そして、クラス名をどこにでも指定することができます。 'foo = Greetings :: hello'; – Roddy

2

残念ながらenumは弱点ですenumタイプの変数はenumタイプですが、enumで宣言した定数はintです。

だからあなたの例では

enum Greetings{ hello, bye, how }; 
enum Testing { one, two, three }; 

enum Greetings const holla = hello; 
enum Testing const eins = one; 

hellooneは2と同じ値の名前、すなわち0、および同じタイプintです。

hollaおよびeinsの値は、それぞれ0ですが、それぞれのタイプがあります。

#define GREETING(VAL) ((enum Greetings){ 0 } = (VAL)) 
#define HELLO GREETING(hello) 

あなたは「本当の」定数のいくつかの「公式」タイプの安全性を強制する場合、それはあなたが望むタイプと値を持つエンティティである、あなたはいくつかのより複雑な構文を使用する必要があると思います

GREETINGマクロの代入は、結果が "rvalue"であることを保証します。したがって、変更することはできず、その型と値のためだけにコンパイラが行います。

1

また、有効範囲を確保したい場合は、整数値を取得するためのポインタ逆参照の小さなオーバーヘッドと、定型入力の多くが含まれています。それは、それが他の方法で必要とされるであろう範囲検査コードを書くことからあなたを助けるので、まだ有用かもしれません。

greetings.h:

#ifndef GREETINGS_H 
#define GREETINGS_H 

struct greetings; 
typedef struct greetings Greetings; 

extern const Greetings * const Greetings_hello; 
extern const Greetings * const Greetings_bye; 
extern const Greetings * const Greetings_how; 

const char *Greetings_str(const Greetings *g); 
int Greetings_int(const Greetings *g); 

#endif 

greetings.c:

#include "greetings.h" 

struct greetings { 
    const int val; 
}; 

static const Greetings hello = { 0 }; 
static const Greetings bye = { 1 }; 
static const Greetings how = { 2 }; 

const Greetings * const Greetings_hello = &hello; 
const Greetings * const Greetings_bye = &bye; 
const Greetings * const Greetings_how = &how; 

static const char * const Greetings_names[] = { 
    "hello", 
    "bye", 
    "how" 
}; 

const char * 
Greetings_str(const Greetings *g) 
{ 
    return Greetings_names[g->val]; 
} 

int 
Greetings_int(const Greetings *g) 
{ 
    return g->val; 
} 

例のmain.c:

#include <stdio.h> 
#include "greetings.h" 

void 
printTest(const Greetings *greeting) 
{ 
    if (greeting == Greetings_how) return; 
    puts(Greetings_str(greeting)); 
} 

int 
main() 
{ 
    const Greetings *g = Greetings_hello; 
    printTest(g); 
} 

はい、タイプするLOTが、あなたは完全な型を取得レンジの安全性。他のコンパイルユニットはstruct greetingsをインスタンス化することはできませんので、あなたは完全に安全です。


編集2015-07-04:NULLから保護するために、2つの可能性があります。既定値(現在使用されているポインタの代わりに#define Greetings_hello 0)にNULLを使用してください。これは非常に便利ですが、デフォルトのenum値の型安全性を損なうので、NULLを任意の列挙型に使用できます。それとも、それは無効宣言し、いずれかのエラーを返す、アクセサメソッドでそれをチェックし、または、例えば中greetings.hコンパイル時にそれをキャッチするためのGCC __attribute__((nonnull()))のようなものを使用します。

const char *Greetings_str(const Greetings *g) 
     __attribute__((nonnull(1))); 
+0

ユーザーが 'null'を渡している場合は、できるだけ早くクラッシュさせ、それらを並べ替えることをお勧めします。または、あなたが言うように、コンパイラ特有のトリックを使用します。私が提案できる1つの小さな改善点は、グリーティング文字列を 'Greetings'構造体に移動することです。これは、インスタンスメンバーのように扱うためです。例えば、 'static const Greetings hello = {0、" hello "};'などのように、実装では 'g-> label'だけを返すことができます。 – Yawar