C ++の 'struct'と 'typedef struct'の違いは?


回答:


1202

C ++では、微妙な違いしかありません。それは違いを生み出すCからの持ち越しです。

C言語標準(C89§3.1.2.3C99§6.2.3、およびC11§6.2.3)では、タグ識別子struct/ union/の場合enum)と通常の識別子typedef他の識別子の場合)を含む、識別子のさまざまなカテゴリに個別の名前空間を義務付けています。。

あなたが言っただけなら:

struct Foo { ... };
Foo x;

Fooはタグの名前空間でのみ定義されているため、コンパイラエラーが発生します。

次のように宣言する必要があります。

struct Foo x;

を参照するFooときはいつでも、それを常にと呼ぶ必要がありstruct Fooます。これはすぐに煩わしいので、以下を追加できますtypedef

struct Foo { ... };
typedef struct Foo Foo;

これでstruct Foo(タグネームスペース内)とプレーンFoo(通常の識別子ネームスペース内)の両方が同じものを参照するようになりFoostructキーワードなしでタイプのオブジェクトを自由に宣言できます。


構成:

typedef struct Foo { ... } Foo;

は宣言の省略形ですtypedef


最終的に、

typedef struct { ... } Foo;

匿名構造を宣言し、typedefそれを作成します。したがって、この構成では、タグの名前空間に名前はなく、typedef名前空間の名前のみが含まれます。つまり、これを前方宣言することもできません。 前方宣言を行う場合は、タグの名前空間で名前を付ける必要があります


C ++では、すべてのstruct/ union/ enum/ class宣言typedefは、名前が同じ名前の別の宣言によって隠されていない限り、暗黙的に'のように動作します。詳細については、Michael Burrの回答を参照してください。


53
あなたの言うことは真実ですが、AFAIKのステートメントは、「typedef struct {...} Foo;」です。名前のない構造体のエイリアスを作成します。
2009年

25
「typedef struct Foo {...} Foo;」には微妙な違いがあります。そして「typedef struct {...} Foo;」。
Adam Rosenfield、

8
Cでは、structタグ、unionタグ、および列挙型タグは、上記のように2つを使用する(structおよびunion)ではなく、1つの名前空間を共有します。typedef名で参照される名前空間は、実際には別のものです。つまり、 'union x {...};'の両方を持つことはできません。そして 'struct x {...};' 単一のスコープで。
ジョナサンレフラー

10
not-quite-typedefとは別に、問題の2つのコードのもう1つの違いは、Fooは最初の例ではコンストラクターを定義できますが、2番目の例ではできません(匿名クラスはコンストラクターまたはデストラクタを定義できないため)。 。
Steve Jessop、

1
@Lazer:微妙な違いがあります、Adamは(彼が言うように) 'Type var'を使用してtypedefなしで変数を宣言できることを意味します。
Fred Nurk、2011

226

、このDDJの記事、ダンサックスはあなたの構造体のtypedefない場合は、バグが通過クリープすることができます一つの小さなエリア説明(およびクラスを!):

必要に応じて、C ++がすべてのタグ名のtypedefを生成すると想像できます。

typedef class string string;

残念ながら、これは完全に正確ではありません。それがそんなに簡単だったらいいのにと思いますが、そうではありません。C ++は、Cとの非互換性を導入せずに、構造体、共用体、または列挙型のそのようなtypedefを生成できません。

たとえば、Cプログラムが関数とstatusという名前の構造体の両方を宣言するとします。

int status(); struct status;

繰り返しになりますが、これは悪い習慣かもしれませんが、Cです。このプログラムでは、ステータス(それ自体)は関数を指します。構造体ステータスはタイプを参照します。

C ++がタグのtypedefを自動的に生成した場合、このプログラムをC ++としてコンパイルすると、コンパイラーは以下を生成します。

typedef struct status status;

残念ながら、この型名は関数名と競合し、プログラムはコンパイルされません。これが、C ++が各タグのtypedefを単純に生成できない理由です。

C ++では、タグはtypedef名と同じように機能しますが、プログラムは、タグと同じ名前と同じスコープを持つオブジェクト、関数、または列挙子を宣言できます。その場合、オブジェクト、関数、または列挙子の名前はタグ名を非表示にします。プログラムは、タグ名の前にキーワードclass、struct、union、またはenum(適切な場合)を使用することによってのみ、タグ名を参照できます。これらのキーワードの1つにタグが続くタイプ名は、エラボレートタイプ指定子です。たとえば、struct statusとenum monthは、精巧な型指定子です。

したがって、次の両方を含むCプログラム:

int status(); struct status;

C ++としてコンパイルした場合も同じように動作します。名前のステータスのみが関数を参照します。プログラムは、精巧なタイプ指定子構造体ステータスを使用することによってのみタイプを参照できます。

では、どのようにすればバグがプログラムに侵入できるのでしょうか。リスト1のプログラムを考えてみてください 。このプログラムは、デフォルトのコンストラクターを持つクラスfooと、fooオブジェクトをchar const *に変換する変換演算子を定義します。表現

p = foo();

mainでは、fooオブジェクトを作成し、変換演算子を適用する必要があります。後続の出力ステートメント

cout << p << '\n';

クラスfooを表示するべきですが、表示しません。関数fooを表示します。

この驚くべき結果は、プログラムにリスト2に示すヘッダーlib.hが含まれているために発生します。このヘッダーは、fooという名前の関数を定義します。関数名fooはクラス名fooを隠すため、mainでのfooへの参照は、クラスではなく関数を参照します。mainは、次のように、精巧な型指定子を使用することによってのみクラスを参照できます。

p = class foo();

プログラム全体でこのような混乱を回避する方法は、クラス名fooに次のtypedefを追加することです。

typedef class foo foo;

クラス定義の直前または直後。このtypedefは、型名fooと関数名foo(ライブラリーから)の間の競合を引き起こし、コンパイル時エラーをトリガーします。

もちろん、これらのtypedefを実際に書いている人は誰もいません。それは多くの規律を必要とします。リスト1のようなエラーの発生率はおそらくかなり小さいので、多くの人がこの問題に悩まされることはありません。ただし、ソフトウェアのエラーによって人身事故が発生する可能性がある場合は、エラーが発生する可能性が低い場合でもtypedefを記述する必要があります。

クラスと同じスコープ内で関数名またはオブジェクト名を使用してクラス名を非表示にしたくなる理由は想像できません。Cの非表示規則は誤りであり、C ++のクラスに拡張されるべきではありませんでした。実際、間違いを修正することはできますが、必要ではない余分なプログラミング規則と労力が必要です。


11
「class foo()」を試して失敗した場合:ISO C ++では、「class foo()」は不正な構成です(記事は標準化される前の'97と書かれているようです)。「typedef class foo foo;」と置くことができます。メインにすると、「foo();」と言うことができます (そのため、typedef-nameは語彙的に関数の名前よりも近いため)。構文的には、T()では、Tは単純型指定子でなければなりません。詳細な型指定子は許可されていません。もちろん、これは良い答えです。
ヨハネスシャウブ-litb 2009

Listing 1Listing 2リンクが壊れています。見てください。
Prasoon Saurav、

3
クラスと関数に異なる命名規則を使用する場合は、余分なtypedefを追加する必要なく、命名の競合を回避できます。
zstewart

64

もう1つの重要な違い:typedefは前方宣言できません。そこらのtypedefオプションあなたがしなければならない#includeファイルが含むtypedefすべてのものを意味し、#includeあなたはsの.hも、それが直接それを必要とするかどうか、というようにするかどうか、そのファイルが含まれています。大規模なプロジェクトのビルド時間に確実に影響を与える可能性があります。

を使用しないtypedef場合struct Foo;.hファイルの先頭に前方宣言を追加#includeし、.cppファイル内の構造体定義のみを追加できる場合があります。


構造体の定義を公開するとビルド時間が影響を受けるのはなぜですか?コンパイラーは、Foo * nextFooのようなものを検出したときに、(typedefオプションを指定してコンパイラーが定義を認識するように)必要がない場合でも追加のチェックを行いますか
リッチ

3
これは実際には余分なチェックではなく、コンパイラーが処理する必要のあるコードです。インクルードチェーンのどこかでそのtypedefに遭遇するすべてのcppファイルについて、typedefをコンパイルします。大規模なプロジェクトでは、typedefを含む.hファイルは何百回もコンパイルされる可能性がありますが、プリコンパイル済みヘッダーは非常に役立ちます。前方宣言の使用を回避できる場合は、完全な構造体仕様を含む.hのインクルードを、本当に重要なコードのみに制限する方が簡単なので、対応するインクルードファイルのコンパイル頻度は低くなります。
Joe

前のコメント者(投稿の所有者以外)に@を送ってください。私はほとんど返事を逃した。しかし、情報をありがとう。
リッチ

これはC11ではもう当てはまりません。同じ名前の同じ構造体を何度もtypedefできるのです。
マイケルマイヤー、2015

32

違いあります、微妙です。このように見てくださいstruct Foo。新しいタイプを紹介します。2つ目は、名前のないstruct型のFoo(新しい型ではなく)と呼ばれるエイリアスを作成します。

7.1.3 typedef指定子

1 [...]

typedef指定子で宣言された名前はtypedef-nameになります。typedef-nameは、その宣言の範囲内で、構文的にキーワードと同等であり、8節で説明した方法で識別子に関連付けられた型に名前を付けます。したがって、typedef-nameは別の型の同義語です。typedef-name 、クラス宣言(9.1)または列挙宣言のように新しい型を導入しません

8 typedef宣言が名前のないクラス(または列挙型)を定義する場合、宣言によってそのクラス型(または列挙型)として宣言された最初のtypedef-nameは、リンク目的でのみクラス型(または列挙型)を示すために使用されます( 3.5)。[例:

typedef struct { } *ps, S; // S is the class name for linkage purposes

そのため、typedefは常に別の型のプレースホルダー/シノニムとして使用されます。


10

typedef構造体では前方宣言を使用できません。

構造体自体は匿名型であるため、転送宣言する実際の名前はありません。

typedef struct{
    int one;
    int two;
}myStruct;

このような前方宣言は機能しません:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types

関数プロトタイプの2番目のエラーは発生しません。なぜ「再定義;異なる基本タイプ」と表示されるのですか?コンパイラーはmyStructの定義がどのように見えるかを知る必要はありませんよね?コードの一部(typedefの1つ、または前方宣言の1つ)を使用しても、myStructは構造体の型を示しますよね?
リッチ

@リッチ名前の衝突があると不満を言っています。「myStructと呼ばれる構造体を探す」という前方宣言があり、次に無名の構造の名前を「myStruct」に変更しているtypedefがあります。
Yochai Timmer

typedefとforward宣言の両方を同じファイルに入れますか?私はそうしました、そしてgccはそれをうまくコンパイルしました。myStructは、名前のない構造として正しく解釈されます。タグmyStructはタグ名前空間にあり、typedef_ed myStructは、関数名やローカル変数名などの他の識別子が存在する通常の名前空間にあります。そのため、競合があってはなりません。エラーが疑われる場合は、コードを表示できます。
リッチ

@Rich GCCが同じエラーを与え、テキストビットを変化:gcc.godbolt.org/...
Yochai Timmer

edの名前をtypedef持つforward宣言だけがある場合typedef、名前のない構造を参照しないことは理解できると思います。代わりに、フォワード宣言はtagで不完全な構造を宣言しmyStructます。また、の定義を見ないとtypedeftypedefed名を使用する関数プロトタイプは無効です。したがってmyStruct、型を表すために使用する必要があるときはいつでも、typedef全体を含める必要があります。私があなたを誤解した場合は私を修正してください。ありがとう。
リッチ

0

C ++での「typedef構造体」と「構造体」の重要な違いは、「typedef構造体」でのインラインメンバーの初期化が機能しないことです。

// the 'x' in this struct will NOT be initialised to zero
typedef struct { int x = 0; } Foo;

// the 'x' in this struct WILL be initialised to zero
struct Foo { int x = 0; };

3
違います。どちらの場合xも初期化されます。ColiruオンラインIDEでのテストを 参照してください(私はそれを42に初期化したので、割り当てが実際に行われたことがゼロよりも明白です)。
Colin D Bennett

実際、Visual Studio 2013でテストしましたが、初期化されていません。これは、プロダクションコードで遭遇した問題でした。すべてのコンパイラは異なり、特定の条件を満たす必要があります。
user2796283

-3

C ++には違いはありませんが、Cでは、明示的に行うことなくstruct Fooのインスタンスを宣言できるようになると思います。

struct Foo bar;

3
@dirkgentlyの答えを見てください--- 違いありますが、それは微妙です。
Keith Pinson、2012

-3

構造体はデータ型を作成することです。typedefは、データ型のニックネームを設定するためのものです。


12
あなたはその質問を理解しませんでした。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.