50


C++ クラスの静的メンバー変数の場合 - 初期化はクラスの外部で行われます。なぜだろう?これに対する論理的な推論/制約はありますか? それとも、純粋にレガシーな実装ですか? 標準ではこれを修正したくないのですか?

クラスで初期化を行うことは、より「直感的」で混乱が少ないと思います。また、変数の静的性とグローバル性の両方の感覚を与えます。たとえば、 static const メンバーが表示されている場合。

4

5 に答える 5

40

これは基本的に、 1 つの定義規則に違反しないように、静的メンバーを 1 つの翻訳単位で定義する必要があるためです。言語が次のようなものを許可する場合:

struct Gizmo
{
  static string name = "Foo";
};

次に、このヘッダー ファイルのname各翻訳単位で定義されます。#include

C++ では、宣言内で統合静的メンバーを定義できますが、それでも 1 つの翻訳単位内に定義を含める必要がありますが、これは単なるショートカットまたは構文糖衣です。したがって、これは許可されます。

struct Gizmo
{
  static const int count = 42;
};

a) 式が整数型または列挙型である限りconst、b) 式はコンパイル時に評価でき、c) 1 つの定義規則に違反しない定義がまだどこかに存在します。

ファイル: gizmo.cpp

#include "gizmo.h"

const int Gizmo::count;
于 2010-12-28T17:02:35.500 に答える
13

C++ では、当初から初期化子の存在はオブジェクト定義の排他的な属性でした。つまり、初期化子を使用した宣言は常に(ほとんどの場合)定義です。

ご存じのとおり、C++ プログラムで使用される各外部オブジェクトは、1 つの翻訳単位で 1 回だけ定義する必要があります。静的オブジェクトのクラス内イニシャライザを許可すると、すぐにこの規則に反します。イニシャライザはヘッダー ファイル (通常はクラス定義が存在する場所) に入り、同じ静的オブジェクトの複数の定義を生成します (ヘッダー ファイルを含む翻訳単位ごとに 1 つ)。 )。もちろん、これは受け入れられません。このため、静的クラス メンバーの宣言方法は完全に「伝統的」のままです。ヘッダー ファイルでのみ宣言し (つまり、初期化子は許可されません)、選択した翻訳単位で定義します (おそらく初期化子を使用)。 )。

この規則の 1 つの例外は、整数型または列挙型の const 静的クラス メンバーに対して作成されました。これは、そのようなエントリが整数定数式 (ICE) の可能性があるためです。ICE の主な考え方は、コンパイル時に評価されるため、関連するオブジェクトの定義に依存しないということです。これが、整数型または列挙型でこの例外が発生する可能性がある理由です。しかし、他の型の場合、C++ の基本的な宣言/定義の原則と矛盾するだけです。

于 2010-12-28T17:22:58.623 に答える
3

これは、コードのコンパイル方法が原因です。多くの場合ヘッダーにあるクラスで初期化する場合、ヘッダーが含まれるたびに静的変数のインスタンスを取得します。これは間違いなく意図ではありません。クラスの外で初期化すると、cpp ファイルで初期化できるようになります。

于 2010-12-28T16:52:18.550 に答える
2

C++ 標準のセクション 9.4.2、静的データ メンバーには、次のように記載されています。

staticデータ メンバーがconst整数型または列挙型の場合const、クラス定義でのその宣言では、整数定数式であるconst-initializerを指定できます。

したがって、静的データメンバーの値を「クラス内」に含めることができます(これは、クラスの宣言内を意味すると思います)。ただし、静的データ メンバーの型は、const整数型またはconst列挙型である必要があります。他の型の静的データ メンバーの値をクラス宣言内で指定できない理由は、重要な初期化が必要になる可能性が高いためです (つまり、コンストラクターを実行する必要があります)。

以下が合法であると想像してください。

// my_class.hpp
#include <string>

class my_class
{
public:
  static std::string str = "static std::string";
//...

このヘッダーを含む CPP ファイルに対応する各オブジェクト ファイルには、my_class::str(バイトで構成される) ためのストレージ スペースのコピーだけでなく、C 文字列を取るコンストラクターをsizeof(std::string)呼び出す "ctor セクション" も含まれます。std::stringのストレージ スペースの各コピーはmy_class::str共通のラベルで識別されるため、リンカは理論的にはストレージ スペースのすべてのコピーを 1 つのコピーにマージできます。ただし、リンカーは、オブジェクト ファイルの ctor セクション内のコンストラクター コードのすべてのコピーを分離することはできません。str次のコンパイルで初期化するすべてのコードを削除するようリンカーに要求するようなものです。

std::map<std::string, std::string> map;
std::vector<int> vec;
std::string str = "test";
int c = 99;
my_class mc;
std::string str2 = "test2";

EDIT次のコードについて、g++ のアセンブラー出力を調べることは有益です。

// SO4547660.cpp
#include <string>

class my_class
{
public:
    static std::string str;
};

std::string my_class::str = "static std::string";

アセンブリ コードは、次を実行して取得できます。

g++ -S SO4547660.cpp

SO4547660.sg++ が生成するファイルを見ると、このような小さなソース ファイルに大量のコードが含まれていることがわかります。

__ZN8my_class3strEのストレージ スペースのラベルですmy_class::str__static_initialization_and_destruction_0(int, int)というラベルが付いた関数のアセンブリ ソースもあり__Z41__static_initialization_and_destruction_0iiます。その関数は g++ にとって特別ですが、g++ は非初期化コードが実行される前に確実に呼び出されることを知っておいてください。この関数の実装が を呼び出すことに注意してください__ZNSsC1EPKcRKSaIcE。これは のマングル記号ですstd::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)

上記の架空の例に戻り、これらの詳細を使用すると、インクルードする CPP ファイルに対応する各オブジェクト ファイルには、バイトmy_class.hppのラベル __ZN8my_class3strEと、関数の実装内で呼び出すアセンブリ コードがあります。リンカーは、すべての を簡単にマージできますが、オブジェクト ファイルの の実装内で呼び出すコードを分離することはできません。sizeof(std::string)__ZNSsC1EPKcRKSaIcE__static_initialization_and_destruction_0(int, int)__ZN8my_class3strE__ZNSsC1EPKcRKSaIcE__static_initialization_and_destruction_0(int, int)

于 2010-12-28T17:40:58.667 に答える
0

classブロックの外で初期化を行う主な理由は、他のクラスメンバー関数の戻り値で初期化できるようにするためだと思います。で初期化a::varする場合は、インクルードするすべてのファイルが最初にインクルードされb::some_static_fn()ていることを確認する必要があります。特に(遅かれ早かれ)循環参照に遭遇した場合、それ以外の場合は不要な. 同じ問題が、すべてをメイン クラスに配置するのではなく、ファイルにクラス メンバー関数の実装を含める主な理由です。.cppa.hb.hinterface.cpp.h

少なくともメンバー関数を使用すると、それらをヘッダーに実装するオプションがあります。変数を使用すると、.cpp ファイルで初期化を行う必要があります。私はその制限にまったく同意しませんし、それには正当な理由もないと思います。

于 2010-12-28T16:59:01.823 に答える