290

私は次のようなことをしようとしています:

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 で内部的に列挙型を宣言し、ビルドされたライブラリ ファイルとヘッダーを人々に配布できるように、列挙型を前方宣言したいと考えました。

コンパイラはGCCです。

4

18 に答える 18

226

列挙型を前方宣言できない理由は、値を知らなければ、コンパイラーが列挙型変数に必要なストレージを認識できないためです。C++ コンパイラは、指定されたすべての値を格納するために必要なサイズに基づいて、実際のストレージ スペースを指定できます。見えるのが前方宣言だけの場合、翻訳単位は選択されたストレージ サイズを知ることができませcharint


ISO C++ 標準のセクション 7.2.5 から:

列挙型の基になる型は、列挙型で定義されたすべての列挙子の値を表すことができる整数型です。列挙子の値がorintに収まらない場合を除き、基になる型が よりも大きくならないことを除いて、どの整数型が列挙型の基になる型として使用されるかは実装定義です。列挙子リストが空の場合、基になる型は、列挙に値 0 の単一の列挙子があるかのようになります。列挙型、列挙型のオブジェクト、または列挙子に適用される値は、に適用される値です。根底にあるタイプ。intunsigned intsizeof()sizeof()

関数の呼び出し元は、呼び出しスタックを正しく設定するためにパラメーターのサイズを知っている必要があるため、列挙リスト内の列挙の数は、関数プロトタイプの前に知っておく必要があります。

アップデート:

C++0X では、enum 型を前方宣言するための構文が提案され、受け入れられています。列挙の前方宣言 (rev.3) で提案を見ることができます。

于 2008-09-16T14:02:47.297 に答える
91

ストレージ型を同時に宣言する限り、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 はこれをサポートしています。以前のバージョンでは、まだ理解していない非標準のサポートがいくつかあります.単純な前方宣言は合法であるという提案がいくつか見つかりましたが、マイレージは異なる場合があります.

于 2012-07-24T13:42:53.053 に答える
15

[私の答えは間違っていますが、コメントが役立つのでここに残しました]。

異なる列挙型へのポインターは同じサイズであることが保証されていないため、列挙型の前方宣言は非標準です。コンパイラは、この型で使用できるポインタのサイズを知るために、定義を確認する必要がある場合があります。

実際には、少なくともすべての一般的なコンパイラでは、列挙型へのポインターは一貫したサイズです。列挙型の前方宣言は、たとえば Visual C++ によって言語拡張として提供されます。

于 2008-09-16T11:48:52.110 に答える
8

実際、enum の前方宣言のようなものはありません。列挙型の定義には、列挙型を使用する他のコードに依存する可能性のあるコードが含まれていないため、通常、最初に宣言するときに列挙型を完全に定義しても問題はありません。

列挙型の唯一の使用がプライベート メンバー関数によるものである場合、列挙型自体をそのクラスのプライベート メンバーとして持つことでカプセル化を実装できます。列挙型は、宣言の時点、つまりクラス定義内で完全に定義する必要があります。ただし、プライベートメンバー関数をそこで宣言することは大きな問題ではなく、それよりも実装内部の悪い露出ではありません。

実装の詳細をより深く隠蔽する必要がある場合は、純粋な仮想関数のみで構成される抽象インターフェイスと、インターフェイスを実装 (継承) する具体的で完全に隠蔽されたクラスに分割できます。クラス インスタンスの作成は、ファクトリまたはインターフェイスの静的メンバー関数によって処理できます。そうすれば、プライベート関数は言うまでもなく、実際のクラス名でさえ公開されません。

于 2008-09-16T11:39:27.563 に答える
4

私はそれをこのようにします:

[パブリックヘッダー内]

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と互換性があります。

于 2008-09-16T13:04:33.520 に答える
2

列挙型をヘッダーファイルに表示したくない場合で、それがプライベートメソッドでのみ使用されるようにする場合、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つは、ヘッダーが非常に単純なため、コンパイル時間が短縮されることです。

ただし、使用するのは面倒なので、ヘッダーで列挙型をプライベートとして宣言するだけで問題が発生するかどうかを自問する必要があります。

于 2008-09-16T23:51:45.837 に答える
2

列挙型を構造体にラップして、いくつかのコンストラクターと型変換を追加し、代わりに構造体を前方宣言することができます。

#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

于 2012-07-08T09:26:54.833 に答える
2

これが(一種の)衝突して以来、いくつかの反対意見があるので、ここに標準からの関連ビットをいくつか示します。調査によると、標準は実際には前方宣言を定義しておらず、列挙型を前方宣言できるかどうかも明示的に述べていません。

まず、dcl.enum のセクション 7.2 から:

列挙型の基になる型は、列挙型で定義されたすべての列挙子の値を表すことができる整数型です。列挙子の値が int または unsigned int に収まらない場合を除き、基になる型が int より大きくないことを除いて、どの整数型が列挙型の基になる型として使用されるかは実装定義です。列挙子リストが空の場合、基になる型は、列挙型に値 0 の単一の列挙子があるかのようになります。列挙型、列挙型のオブジェクト、または列挙子に適用される sizeof() の値は、の値です。基になる型に適用される sizeof()。

したがって、列挙型の基になる型は実装定義であり、1 つの小さな制限があります。

次に、「不完全な型」(3.9) のセクションに移ります。これは、前方宣言に関する標準に最も近いものです。

宣言されているが定義されていないクラス、または不明なサイズの配列または不完全な要素型の配列は、不完全に定義されたオブジェクト型です。

クラス タイプ (「クラス X」など) は、翻訳単位のある時点で不完全であり、後で完全になる場合があります。タイプ「クラス X」は両方の点で同じタイプです。配列オブジェクトの宣言された型は、不完全なクラス型の配列である可能性があり、したがって不完全です。クラス型が後で翻訳単位で完了すると、配列型は完全になります。これら 2 点の配列型は同じ型です。配列オブジェクトの宣言された型は、未知のサイズの配列である可能性があるため、翻訳単位のある時点で不完全であり、後で完全になります。この 2 点の配列型 (「T の境界が不明な配列」と「N T の配列」) は異なる型です。サイズが不明な配列へのポインターの型、またはサイズが不明な配列であると typedef 宣言によって定義された型の型。

そのため、標準では、前方宣言できる型がほとんどレイアウトされています。Enum は存在しなかったため、コンパイラの作成者は一般に、基になる型の可変サイズのために前方宣言が標準で許可されていないと見なしています。

それも理にかなっています。列挙型は通常、値渡しの状況で参照され、コンパイラは実際にそのような状況でストレージ サイズを知る必要があります。ストレージ サイズは実装定義であるため、多くのコンパイラは、すべての列挙型の基になる型に 32 ビット値を使用することを選択するだけで、その時点でそれらを前方宣言することが可能になります。

興味深い実験として、Visual Studio で列挙型を前方宣言し、上で説明したように sizeof(int) より大きい基になる型を強制的に使用して、何が起こるかを確認することができます。

于 2009-03-26T10:41:55.767 に答える
1

私のプロジェクトでは、名前空間にバインドされた列挙手法を採用して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

于 2009-06-13T16:32:48.280 に答える
1

GCCでは前方宣言できないようです!

ここに興味深い議論があります

于 2008-09-16T11:37:29.173 に答える
1

VC++の場合、前方宣言と基になる型の指定に関するテストは次のとおりです。

  1. 次のコードは正常にコンパイルされます。
    typedef int myint;
    列挙型 T ;
    void foo(T * tp )
    {
        * tp = (T)0x12345678;
    }
    列挙型 T : char
    {
        あ
    };

しかし、/W4/W3この警告は発生しません)の警告が表示されました

警告 C4480: 非標準の拡張機能が使用されています: 列挙型 'T' の基になる型を指定しています

  1. 上記の場合、VC++ (Microsoft (R) 32 ビット C/C++ 最適化コンパイラ バージョン 15.00.30729.01 for 80x86) はバグがあるように見えます。
  • 列挙型 T を見たとき。VC は、列挙型 T が基になる型として既定の 4 バイトの int を使用することを前提としているため、生成されるアセンブリ コードは次のようになります。
    ?foo@@YAXPAW4T@@@Z PROC ; ふー
    ; ファイル e:\work\c_cpp\cpp_snippet.cpp
    ; 13行目
        EBPをプッシュ
        mov ebp、esp
    ; 14行目
        mov eax, DWORD PTR _tp$[ebp]
        mov DWORD PTR [eax], 305419896 ; 12345678H
    ; 15行目
        ポップEBP
        ret 0
    ?foo@@YAXPAW4T@@@Z ENDP ; ふー

上記のアセンブリ コードは、個人的な推測ではなく、/Fatest.asm から直接抽出したものです。

見えますか

mov DWORD PTR[eax], 305419896        ; 12345678H

ライン?

次のコード スニペットはそれを証明しています。

    int main(int argc, char *argv)
    {
        組合{
            char ca[4];
            Tt;
        }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] ) ;
        0 を返します。
    }

結果は次のとおりです。

0x78、0x56、0x34、0x12

  • enum T の前方宣言を削除し、enum T の定義の後に関数 foo の定義を移動した後: 結果は OK です:

上記の重要な命令は次のようになります。

mov BYTE PTR [eax], 120 ; 00000078H

最終結果は次のとおりです。

0x78、0x1、0x1、0x1

値は上書きされないことに注意してください。

そのため、VC++ で enum の前方宣言を使用することは有害であると見なされます。

ところで、当然のことながら、基になる型の宣言の構文は C# と同じです。実際には、メモリが制限されている組み込みシステムと通信するときに、基になる型を char として指定することで 3 バイトを節約する価値があることがわかりました。

于 2009-04-04T19:15:45.657 に答える
0

あなたの問題に対する私の解決策は、次のいずれかになります。

1-列挙型の代わりに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のいずれかです。

于 2008-09-16T23:46:03.917 に答える
-1

列挙型を定義して、型の要素の可能な値を制限されたセットに制限します。この制限は、コンパイル時に適用されます。

後で「限定セット」を使用するという事実を事前に宣言しても、何の価値もありません。後続のコードは、それから利益を得るために、可能な値を知る必要があります。

コンパイラ列挙型のサイズを考慮しますが、列挙型を前方宣言すると、列挙型の意図が失われます。

于 2009-01-27T13:05:06.963 に答える
-1

列挙型はさまざまなサイズの整数サイズになる可能性があるため (コンパイラーが特定の列挙型のサイズを決定します)、列挙型へのポインターもさまざまなサイズを持つことができます。例えば)。

したがって、コンパイラは、列挙型を前方宣言し、それへのポインターをユーザーに許可することさえできません。その場合でも、列挙型のサイズが必要だからです。

于 2008-09-16T11:41:55.653 に答える