質問
私は次のようなことをしようとしています:
enum E;
void Foo(E e);
enum E {A, B, C};
コンパイラが拒否します。Google でざっと調べてみたところ、「それはできない」という意見が一致しているようですが、その理由がわかりません。誰か説明してもらえますか?
説明 2:クラス内に上記の列挙型を受け取るプライベートメソッドがあり、列挙型の値を公開したくないのでこれを行っています。つまり、たとえば、E が次のように定義されていることを誰にも知られたくないのです。
enum E {
FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}
プロジェクト X はユーザーに知ってもらいたいものではないからです。
そこで、ヘッダー ファイルにプライベート メソッドを配置し、cpp の内部で enum を宣言し、構築されたライブラリ ファイルとヘッダーを人々に配布できるように、enum を前方宣言したいと思いました。
コンパイラに関しては、GCC です。
解決
enum を前方宣言できない理由は、値が分からないとコンパイラが enum 変数に必要なストレージを知ることができないためです。C++ コンパイラでは、指定されたすべての値を含めるのに必要なサイズに基づいて実際の記憶領域を指定できます。表示されているのが前方宣言だけである場合、変換単位はどの記憶域サイズが選択されるかを知ることができません。それは char か int、またはその他の可能性があります。
ISO C++ 標準のセクション 7.2.5 より:
の 基礎となるタイプ 列挙型の は、列挙型で定義されたすべての列挙子の値を表すことができる整数型です。どの整数型が列挙型の基礎となる型として使用されるかは実装で定義されます。ただし、基礎となる型が以下の値を超えてはなりません。
int
列挙子の値がフィールドに収まらない場合を除き、int
またはunsigned int
. 。もし 列挙子リスト が空の場合、基になる型は、列挙型に値 0 の単一の列挙子があるかのようになります。の値sizeof()
列挙型、列挙型のオブジェクト、または列挙子に適用されるのは、次の値です。sizeof()
基礎となる型に適用されます。
以来、 発信者 関数にとって、コール スタックを正しく設定するにはパラメーターのサイズを知る必要があり、関数のプロトタイプを作成する前に、列挙リスト内の列挙の数を知る必要があります。
アップデート:C++0X では、列挙型を前方宣言するための構文が提案され、受け入れられています。提案書は次の場所でご覧いただけます。 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf
他のヒント
C++0x では enum の前方宣言も可能です。以前は、列挙型を前方宣言できなかった理由は、列挙型のサイズがその内容に依存するためでした。列挙型のサイズがアプリケーションによって指定されている限り、前方宣言できます。
enum Enum1; //Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int; //Legal in C++0x.
enum class Enum3; //Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short; //Illegal in C++0x, because Enum2 was previously declared with a different type.
最近の展開を考慮して、ここに最新の回答を追加します。
C++11 では、その記憶域型を同時に宣言する限り、列挙型を前方宣言できます。構文は次のようになります。
enum E : short;
void foo(E e);
....
enum E : short
{
VALUE_1,
VALUE_2,
....
}
実際、関数が列挙型の値を参照しない場合は、その時点で完全な宣言はまったく必要ありません。
これは G++ 4.6 以降でサポートされています (-std=c++0x
または -std=c++11
より新しいバージョンでは)。Visual C++ 2013 はこれをサポートしています。以前のバージョンでは、私がまだ理解していないある種の非標準サポートがありました。単純な前方宣言は合法であるという提案をいくつか見つけましたが、YMMV。
C++ で前方宣言することは非常に便利です。 コンパイル時間を大幅に短縮します. 。C++ では、次のようないくつかのものを前方宣言できます。 struct
, class
, function
, 、など...
しかし、前方宣言することはできますか? enum
C++で?
いいえ、できません。
しかし、なぜそれを許可しないのでしょうか?それが許可されていれば、あなたのものを定義できます enum
ヘッダー ファイルを入力し、 enum
ソースファイル内の値。それは許可されるべきだと思われますか?
間違っている。
C++ にはデフォルトの型がありません。 enum
C# (int) と同様です。C++ では、 enum
type は、コンパイラによって、ユーザーの値の範囲に適合する任意の型であると判断されます。 enum
.
それはどういう意味ですか?
つまり、あなたの enum
の基になる型は、すべての値を取得するまで完全には決定できません。 enum
定義されています。宣言と定義を分離できないのはどれですか enum
. 。したがって、前方宣言することはできません。 enum
C++で。
ISO C++ 標準 S7.2.5:
列挙の基になる型は、列挙で定義されたすべての列挙子の値を表すことができる整数型です。どの整数型が列挙型の基礎となる型として使用されるかは実装で定義されます。ただし、基礎となる型が以下の値を超えてはなりません。
int
列挙子の値がフィールドに収まらない場合を除き、int
またはunsigned int
. 。enumerator-list が空の場合、基になる型は、列挙型に値 0 の単一の列挙子があるかのようになります。の値sizeof()
列挙型、列挙型のオブジェクト、または列挙子に適用されるのは、次の値です。sizeof()
基礎となる型に適用されます。
C++ で列挙型のサイズを決定するには、 sizeof
オペレーター。列挙型のサイズは、その基になる型のサイズとなります。このようにして、コンパイラがどの型を使用しているかを推測できます。 enum
.
タイプを指定するとどうなるでしょうか enum
明示的に次のようにします:
enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);
それでは、前方宣言していただけますか enum
?
いいえ。しかし、なぜそうではないのでしょうか?
のタイプを指定する enum
実際には現在の C++ 標準の一部ではありません。VC++の拡張機能です。ただし、これは C++0x の一部になります。
[私の答えは間違っていますが、コメントは役に立つのでここに残しておきます]。
異なる列挙型へのポインタが同じサイズであることが保証されていないため、列挙型の前方宣言は標準ではありません。コンパイラーは、この型で使用できるポインターのサイズを知るために定義を参照する必要がある場合があります。
実際には、少なくともすべての一般的なコンパイラでは、列挙型へのポインタのサイズは一定です。enum の前方宣言は、Visual C++ などの言語拡張機能として提供されています。
確かに、enum の前方宣言などというものはありません。enum の定義には、enum を使用する他のコードに依存するコードが含まれていないため、最初に宣言するときに enum を完全に定義しても通常は問題ありません。
enum の唯一の使用がプライベート メンバー関数による場合は、enum 自体をそのクラスのプライベート メンバーとして持つことでカプセル化を実装できます。enum は宣言の時点、つまりクラス定義内で完全に定義されている必要があります。ただし、これはそこでプライベート メンバー関数を宣言することほど大きな問題ではなく、実装の内部構造が露呈するほどひどい問題でもありません。
実装の詳細をより高度に隠蔽する必要がある場合は、純粋な仮想関数のみで構成される抽象インターフェイスと、そのインターフェイスを実装 (継承) する完全に隠蔽された具体的なクラスに分割できます。クラス インスタンスの作成は、ファクトリまたはインターフェイスの静的メンバー関数によって処理できます。そうすれば、プライベート関数はもちろん、実際のクラス名さえも公開されなくなります。
その理由は実際には は enum のサイズは前方宣言後はまだ不明です。構造体の前方宣言を使用すると、ポインタを渡したり、前方宣言された構造体定義自体で参照されている場所からオブジェクトを参照したりすることができます。
enum を値渡しできるようにしたいため、enum を前方宣言することはあまり役に立ちません。最近、一部のプラットフォームでは int やlong とは異なるサイズのポインタを char に使用していると聞いたので、それへのポインタを持つことさえできませんでした。したがって、それはすべて列挙型の内容に依存します。
現在の C++ 標準では、次のようなことを明示的に禁止しています。
enum X;
(で 7.1.5.3/1
)。しかし、来年予定されている次の C++ 標準では次のことが許可されており、実際に問題があると確信しました。 もっている 基礎となる型を扱うには:
enum X : int;
これは「不透明な」列挙型宣言として知られています。Xを使用することもできます 値による 次のコードで。そして、その列挙子は、後の列挙型の再宣言で定義できます。見る 7.2
現在の作業草案では。
私ならこうします:
[パブリックヘッダー内]
typedef unsigned long E;
void Foo(E e);
[内部ヘッダー内]
enum Econtent { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X,
FORCE_32BIT = 0xFFFFFFFF };
FORCE_32BIT を追加することで、Econtent が Long にコンパイルされるようになり、E と交換可能になります。
GCCでは前方宣言できないようです!
興味深い議論 ここ
enum をヘッダー ファイルに表示したくない、かつプライベート メソッドでのみ使用されるようにしたい場合、解決策の 1 つは pimpl 原則を使用することです。
これは、次のように宣言するだけで、ヘッダー内のクラス内部を確実に非表示にする手法です。
class A
{
public:
...
private:
void* pImpl;
};
次に、実装ファイル (cpp) で、内部の表現となるクラスを宣言します。
class AImpl
{
public:
AImpl(A* pThis): m_pThis(pThis) {}
... all private methods here ...
private:
A* m_pThis;
};
クラス コンストラクターで実装を動的に作成し、デストラクターで削除する必要があります。パブリック メソッドを実装する場合は、次を使用する必要があります。
((AImpl*)pImpl)->PrivateMethod();
pimpl を使用することには利点があります。その 1 つは、クラス ヘッダーをその実装から切り離し、1 つのクラスの実装を変更するときに他のクラスを再コンパイルする必要がないことです。もう 1 つは、ヘッダーが非常にシンプルであるため、コンパイル時間が短縮されることです。
しかし、これを使用するのは面倒なので、ヘッダーで列挙型をプライベートとして宣言するだけでそれほど面倒なのかをよく自問する必要があります。
いくつかのコンストラクターと型変換を追加して列挙型を構造体にラップし、代わりに構造体を前方宣言することができます。
#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
enum e { VALUES }; \
explicit NAME(TYPE v) : val(v) {} \
NAME(e v) : val(v) {} \
operator e() const { return e(val); } \
private:\
TYPE val; \
}
これは機能するようです:http://ideone.com/TYtP2
これが(ある種)衝突されて以来、いくつかの反対意見があるため、標準から関連する部分をいくつか紹介します。調査によると、この標準では実際には前方宣言が定義されておらず、列挙型が前方宣言できるかどうかも明示されていません。
まず、dcl.enum のセクション 7.2 から:
列挙の根底にあるタイプは、列挙で定義されたすべての列挙者値を表すことができる積分タイプです。列挙器の値がintまたはunsigned intに適合できない場合を除き、基礎となるタイプがintよりも大きくないことを除いて、列挙の基礎型として使用される積分タイプは、実装定義です。列挙者のリストが空の場合、基礎となるタイプは、列挙に値0の単一の列挙器があるかのようです。列挙タイプ、列挙型のオブジェクト、または列挙型に適用されるsizeof()の値は、基礎となるタイプに適用されるsizeof()の値です。
したがって、列挙型の基になる型は実装によって定義されますが、1 つの小さな制限があります。
次に、「不完全型」(3.9) に関するセクションに移ります。これは、前方宣言に関する標準にほぼ近いものです。
宣言されているが定義されていないクラス、または不明なサイズまたは不完全な要素タイプの配列は、不完全に定義されたオブジェクトタイプです。
クラスタイプ(「クラスX」など)は、翻訳ユニットのある時点で不完全であり、後で完了する可能性があります。型「クラス X」は両方の時点で同じ型です。宣言されたタイプの配列オブジェクトは、不完全なクラスタイプの配列である可能性があるため、不完全です。翻訳ユニットでクラスタイプが後で完了すると、配列タイプが完了します。これら 2 つの点の配列型は同じ型です。宣言されたタイプの配列オブジェクトは未知のサイズの配列である可能性があるため、翻訳ユニットのある時点で不完全であり、後で完了します。これら2つのポイント(「tの未知の境界の配列」と「NTの配列」)の配列タイプは、異なるタイプです。未知のサイズの配列へのポインターのタイプ、またはtypedef宣言によって定義されたタイプのタイプは、未知のサイズの配列であるとは完了できません。
つまり、標準では前方宣言できる型がほぼ規定されています。Enum は存在しなかったため、コンパイラの作成者は一般に、基礎となる型の可変サイズのために前方宣言は標準で許可されていないとみなしています。
それも当然です。Enum は通常、値渡しの状況で参照され、コンパイラーは実際にそのような状況でのストレージ サイズを知る必要があります。ストレージ サイズは実装で定義されるため、多くのコンパイラーは、すべての enum の基礎となる型に 32 ビット値を使用することを選択するだけで、その時点でそれらを前方宣言することが可能になります。興味深い実験としては、Visual Studio で列挙型を前方宣言してから、上で説明したように、sizeof(int) より大きい基になる型を強制的に使用して、何が起こるかを確認することが考えられます。
VC の場合、前方宣言と基になる型の指定に関するテストは次のとおりです。
- 次のコードは正常にコンパイルされます。
typedef int myint; enum T ; void foo(T * tp ) { * tp = (T)0x12345678; } enum T : char { A };
ただし、/W4 では警告が表示されました (/W3 ではこの警告は発生しません)
警告 C4480:非標準の拡張子が使用されています:列挙型 'T' の基になる型を指定しています
VC(Microsoft(R)32-Bit C/C ++最適化コンパイラバージョン15.00.30729.01 for 80x86)上記の場合はバギーに見えます。
- enum T を見たとき;VC は、列挙型 T が基礎となる型としてデフォルトの 4 バイト int を使用すると想定するため、生成されるアセンブリ コードは次のようになります。
?foo@@YAXPAW4T@@@Z PROC ; foo ; File e:\work\c_cpp\cpp_snippet.cpp ; Line 13 push ebp mov ebp, esp ; Line 14 mov eax, DWORD PTR _tp$[ebp] mov DWORD PTR [eax], 305419896 ; 12345678H ; Line 15 pop ebp ret 0 ?foo@@YAXPAW4T@@@Z ENDP ; foo
上記のアセンブリ コードは /Fatest.asm から直接抽出されたものであり、私の個人的な推測ではありません。mov dword ptr [eax]、305419896が表示されますか。12345678Hライン?
次のコード スニペットがそれを証明しています。
int main(int argc, char *argv) { union { char ca[4]; T t; }a; a.ca[0] = a.ca[1] = a.[ca[2] = a.ca[3] = 1; foo( &a.t) ; printf("%#x, %#x, %#x, %#x\n", a.ca[0], a.ca[1], a.ca[2], a.ca[3] ); return 0; }
結果は次のとおりです。0x78、0x56、0x34、0x12
- enum T の前方宣言を削除し、関数 foo の定義を enum T の定義の後に移動した後、次のようになります。結果はOKです:
上記の重要な命令は次のようになります。
mov BYTE PTR [eax]、120 ;00000078H
最終結果は次のとおりです。0x78、0x1、0x1、0x1
値は上書きされないことに注意してください
したがって、VC で enum の前方宣言を使用することは有害であると考えられます。
ところで、驚くには当たりませんが、基になる型の宣言の構文は C# の構文と同じです。実際には、メモリが限られている組み込みシステムと通信するときに、基になる型を char として指定することで 3 バイトを節約する価値があることがわかりました。
私のプロジェクトでは、 名前空間にバインドされた列挙 対処するテクニック enum
レガシーコンポーネントとサードパーティコンポーネントからのもの。以下に例を示します。
forward.h:
namespace type
{
class legacy_type;
typedef const legacy_type& type;
}
enum.h:
// May be defined here or pulled in via #include.
namespace legacy
{
enum evil { x , y, z };
}
namespace type
{
using legacy::evil;
class legacy_type
{
public:
legacy_type(evil e)
: e_(e)
{}
operator evil() const
{
return e_;
}
private:
evil e_;
};
}
foo.h:
#include "forward.h"
class foo
{
public:
void f(type::type t);
};
foo.cc:
#include "foo.h"
#include <iostream>
#include "enum.h"
void foo::f(type::type t)
{
switch (t)
{
case legacy::x:
std::cout << "x" << std::endl;
break;
case legacy::y:
std::cout << "y" << std::endl;
break;
case legacy::z:
std::cout << "z" << std::endl;
break;
default:
std::cout << "default" << std::endl;
}
}
main.cc:
#include "foo.h"
#include "enum.h"
int main()
{
foo fu;
fu.f(legacy::x);
return 0;
}
注意してください。 foo.h
ヘッダーは何も知る必要はありません legacy::evil
. 。従来のタイプを使用するファイルのみ legacy::evil
(ここ:main.cc) を含める必要があります enum.h
.
あなたの問題に対する私の解決策は次のいずれかです。
1 - enum の代わりに int を使用します。CPP ファイルの匿名名前空間で int を宣言します (ヘッダーではありません)。
namespace
{
const int FUNCTIONALITY_NORMAL = 0 ;
const int FUNCTIONALITY_RESTRICTED = 1 ;
const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
}
メソッドはプライベートであるため、誰もデータをいじることはありません。さらに、誰かが無効なデータを送信したかどうかをテストすることもできます。
namespace
{
const int FUNCTIONALITY_begin = 0 ;
const int FUNCTIONALITY_NORMAL = 0 ;
const int FUNCTIONALITY_RESTRICTED = 1 ;
const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
const int FUNCTIONALITY_end = 3 ;
bool isFunctionalityCorrect(int i)
{
return (i >= FUNCTIONALITY_begin) && (i < FUNCTIONALITY_end) ;
}
}
2:Java で行われるように、限定された const インスタンス化を使用して完全なクラスを作成します。クラスを前方宣言してから CPP ファイル内で定義し、列挙型の値のみをインスタンス化します。私はそのようなことを C++ で実行しましたが、列挙型をシミュレートするためのコード (コピー構築、演算子 = など) が必要だったため、結果は期待したほど満足のいくものではありませんでした。
3 :以前に提案したように、プライベートに宣言された列挙型を使用します。ユーザーには完全な定義が表示されますが、それを使用したり、プライベート メソッドを使用したりすることはできません。したがって、通常は、クラスを使用してコードを再コンパイルすることなく、列挙型と既存のメソッドの内容を変更できます。
私の推測では、解決策 3 または 1 のいずれかになると思います。
enum はさまざまなサイズの整数にすることができるため (特定の enum のサイズはコンパイラによって決定されます)、enum へのポインタも整数型であるため、さまざまなサイズを持つことができます (プラットフォームによっては、char のポインタが異なるサイズになります)例えば)。
そのため、コンパイラは列挙型を前方宣言してそれへのポインタを使用することさえできません。なぜなら、そこでさえ列挙型のサイズが必要だからです。
列挙を定義して、その型の要素の取り得る値を限定されたセットに制限します。この制限はコンパイル時に適用されます。
後で「限定セット」を使用するという事実を前方宣言しても、何の価値も追加されません。後続のコードは、その利点を得るために可能な値を知る必要があります。
コンパイラですが、 は 列挙型のサイズが気になる場合は、 意図 列挙型を前方宣言すると失われます。