私は1 enum
以上をお持ちの場合は、例えば:Cの型保証された列挙型?
enum Greetings{ hello, bye, how };
enum Testing { one, two, three };
どのように私は正しいenum
の使用を強制することができますか? たとえば、デバッグと読みやすくするために、one
を使用する必要がある場合、誰かがhello
を使用することを望ましくありません。
私は1 enum
以上をお持ちの場合は、例えば:Cの型保証された列挙型?
enum Greetings{ hello, bye, how };
enum Testing { one, two, three };
どのように私は正しいenum
の使用を強制することができますか? たとえば、デバッグと読みやすくするために、one
を使用する必要がある場合、誰かがhello
を使用することを望ましくありません。
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のバージョンを使用していますことに注意してください。
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);
~~~~~~~~~~~ ^~~~~
ユーザーは、コンパイラからエラーや警告を無視しようとしている場合は、あなたがそれらを助けるために行うことができますあまりありません。
しかし悲しいことに、gccはCモードでも同様の警告を出さない。私はmsvcがそうするかどうかわからない。 gccをどのように欺くかについてのいくつかのコメントがあります(http://stackoverflow.com/questions/4669454/how-to-make-gcc-warn-about-passing-wrong-enum-to-a-function)。読みやすさを犠牲にして警告を出す。 –
これはあなたが聞きたくない回答です。 Cでは、あなたは本当にできません。あなたのCコードがC++の "Clean C"サブセットにある場合は、C++コンパイラでコンパイルして間違ったenum/int値などのエラーをすべて得ることができます。
C++の場合でも、単純ではありません.C++ 11の "enum class"機能が必要です。そして、クラス名をどこにでも指定することができます。 'foo = Greetings :: hello'; – Roddy
残念ながら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;
hello
とone
は2と同じ値の名前、すなわち0
、および同じタイプint
です。
holla
およびeins
の値は、それぞれ0
ですが、それぞれのタイプがあります。
#define GREETING(VAL) ((enum Greetings){ 0 } = (VAL))
#define HELLO GREETING(hello)
:
あなたは「本当の」定数のいくつかの「公式」タイプの安全性を強制する場合、それはあなたが望むタイプと値を持つエンティティである、あなたはいくつかのより複雑な構文を使用する必要があると思います
GREETING
マクロの代入は、結果が "rvalue"であることを保証します。したがって、変更することはできず、その型と値のためだけにコンパイラが行います。
また、有効範囲を確保したい場合は、整数値を取得するためのポインタ逆参照の小さなオーバーヘッドと、定型入力の多くが含まれています。それは、それが他の方法で必要とされるであろう範囲検査コードを書くことからあなたを助けるので、まだ有用かもしれません。
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)));
ユーザーが 'null'を渡している場合は、できるだけ早くクラッシュさせ、それらを並べ替えることをお勧めします。または、あなたが言うように、コンパイラ特有のトリックを使用します。私が提案できる1つの小さな改善点は、グリーティング文字列を 'Greetings'構造体に移動することです。これは、インスタンスメンバーのように扱うためです。例えば、 'static const Greetings hello = {0、" hello "};'などのように、実装では 'g-> label'だけを返すことができます。 – Yawar
これはきちんとしていますが、switch文のenumを少し醜いものにしてしまいます。 – Toby