複数ある場合enum
、例:
enum Greetings{ hello, bye, how };
enum Testing { one, two, three };
正しい使用法を強制するにはどうすればよいenum
ですか?たとえば、デバッグと読みやすさを向上させるために、誰かが使用hello
する必要があるときに使用したくありません。one
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);
}
これによりオーバーヘッドが発生することはなく、警告の代わりにエラーが発生します。
$ gcc -c -std = c99 -Wall -Wextra test2.c test2.c:関数'test'内: test2.c:19:エラー:「takes_greeting」の引数1に互換性のないタイプ test2.c:20:エラー:「takes_number」の引数1に互換性のないタイプ
私はGNU拡張機能を使用しておらず、誤った警告は生成されないことに注意してください。エラーのみ。また、汚れと同じくらい古いバージョンのGCCを使用していることにも注意してください。
$ gcc --version powerpc-apple-darwin9-gcc-4.0.1(GCC)4.0.1(Apple Inc.ビルド5493) Copyright(C)2005 Free Software Foundation、Inc. これは自由ソフトウェアです。コピー条件については、ソースを参照してください。ありません 保証; 商品性や特定の目的への適合性についてもそうではありません。
これは、C99の複合リテラルをサポートするすべてのコンパイラで機能するはずです。
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);
~~~~~~~~~~~ ^~~~~
ユーザーがコンパイラーからのエラーや警告を無視しようとしている場合、ユーザーを支援するためにできることはあまりありません。
残念ながらenum
、Cの型システムの弱点です。型の変数は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
は、同じ値、つまり0
、と同じタイプの2つの名前ですint
。
holla
とeins
再び値0
がありますが、それぞれのタイプがあります。
「実際の」定数、つまり必要なタイプと値を持つエンティティに対して「公式」タイプの安全性を強制したい場合は、より複雑な構造を使用する必要があります。
#define GREETING(VAL) ((enum Greetings){ 0 } = (VAL))
#define HELLO GREETING(hello)
マクロでの割り当てGREETING
により、結果が「右辺値」になることが保証されるため、変更することはできず、コンパイラーはその型と値のみを取得します。
これはあなたが聞きたくない答えです。Cでは、実際にはできません。これで、CコードがC ++の「CleanC」サブセットに含まれている場合、C ++コンパイラでコンパイルして、間違ったenum/int値などを使用した場合のすべてのエラーを取得できます。
有効な範囲も確保したい場合は、整数値を取得するためのポインター逆参照のわずかなオーバーヘッドと、多くの定型文の入力を伴う手法があります。それ以外の場合に必要となる範囲チェックコードを作成することからあなたを活用するので、それはまだ役に立つかもしれません。
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);
}
はい、タイプするのはたくさんですが、完全なタイプと範囲の安全性が得られます。他のコンパイルユニットはインスタンス化できないstruct greetings
ため、完全に安全です。
編集2015-07-04:NULLから保護するには、2つの可能性があります。#define Greetings_hello 0
デフォルト値には(現在使用されているポインターの代わりに)NULLを使用してください。これは非常に便利ですが、デフォルトの列挙値の型安全性が低下します。NULLは任意の列挙に使用できます。または、無効であると宣言し、アクセサメソッドでチェックしてエラーを返すか、GCCなどを使用__attribute__((nonnull()))
してコンパイル時にそれをキャッチします(例:greetings.h)。
const char *Greetings_str(const Greetings *g)
__attribute__((nonnull(1)));
列挙型をtypedefしてから、それらの型の変数と関数の引数を宣言できます。