web-dev-qa-db-ja.com

ヘッダーファイルのconst変数と静的初期化の大失敗

静的変数の初期化に関する多くの質問を読んだ後、これが名前空間レベルのconst変数にどのように適用されるかはまだわかりません。

headerファイルに次のようなコードがありますconfig.hビルドスクリプトによって生成されます:

static const std::string path1 = "/xyz/abc";
static const std::string path2 = "/etc";

私が読んだことによると、staticキーワードは必要ありませんが、ここでは非推奨です。

私の質問:上記のコードは静的初期化の大失敗を起こしやすいですか?

ヘッダーファイルに次のものがある場合myclass.h

class MyClass
{
public:
    MyClass(const std::string& str) : m_str(str) {}
    std::string Get() const { return m_str; }

private:
    std::string m_str;
}

const MyClass myclass1("test");

これは静的初期化で問題を引き起こしますか?

私が正しく理解していれば、const変数には内部リンケージがあるため、どちらの場合も問題はないはずですか?

編集:(ドリビアの回答による)

たぶん私は次のようなユースケースに興味があることを言及する必要があります:

main.cpp

#include <config.h>
#include <myclass.h>

std::string anotherString(path1 + myclass1.Get());

int main()
{
    ...
}

このユースケースに関する別の質問:コンパイラは最適化するかpath2 この場合?

14
Hanno S.

C++ 03標準ドキュメントから必要な情報を正しく取得しようとしました。これが私が見つけたものです:

_const static_宣言に関して:

セクション3.5.3によると、名前空間レベルで定義され、constと宣言されたオブジェクトには、デフォルトで内部リンケージがあります。 staticは、名前空間レベルのオブジェクトが内部リンケージを持つことも宣言しているため、オブジェクトを宣言する必要はありません_static const_。

附属書D.2にも準拠

名前空間スコープでオブジェクトを宣言する場合、staticキーワードの使用は非推奨になります(3.3.5を参照)。

静的初期化の大失敗に関して:

変数はヘッダーファイルで定義されているため、変数を使用する他の静的オブジェクトの前に常に定義されます。

セクション3.6.2.1から:

同じ変換ユニットの名前空間スコープで定義され、動的に初期化される静的ストレージ期間を持つオブジェクトは、その定義が変換ユニットに表示される順序で初期化されるものとします。

回答1:これは、変数を静的オブジェクトコンストラクターに渡すことで問題がないことを意味します。

回答2:ただし、変数が静的オブジェクトの非インラインコンストラクターから参照されている場合、問題が発生する可能性があります。

セクション3.6.2.1でも3.6.2.3でも、異なるコンパイル単位の静的オブジェクトが初期化される順序が指定されていませんif動的初期化が実行される前にmainの最初のステートメント。

次のことを考慮してください。

_// consts.h
#include <string>

const std::string string1 = "ham";
const std::string string2 = "cheese";

// myclass.h
#include <string>

class MyClass
{
public:
    MyClass();
    MyClass(std::string str);
    std::string Get() { return memberString; }
private:
    std::string memberString;
}

// myclass.cpp
#include "consts.h"
#include "myclass.h"

MyClass::MyClass() : memberString(string1) {}

MyClass::MyClass(std::string str) : memberString(str) {}

// main.cpp
#include <iostream>
#include "consts.h"
#include "myclass.h"

MyClass myObject1;
MyClass myObject2(string2);

using namespace std;

int main()
{
    cout << myObject1.Get(); // might not print "ham"
    cout << myObject2.Get(); // will always print "cheese"
}
_

_myclass.cpp_にはconst変数の独自のコピーがあるため、MyClass::MyClass()が呼び出されたときにこれらが初期化されない場合があります。

そうです、ヘッダーファイルで定義されたconst変数は、静的初期化の大失敗を起こしやすい方法で使用できます

私が見る限り、これは静的初期化を必要としない変数にのみ適用されます。

C++ 03標準、セクション3.6.2.1から:

定数式(5.19)で初期化された静的ストレージ期間を持つPODタイプ(3.9)のオブジェクトは、動的初期化が行われる前に初期化される必要があります。

9
Hanno S.

最初の定義では、path1を含む各コンパイル単位にconfig.hを配置します。これを回避するには、ヘッダーファイルで変数を定義しないでください。通常、ヘッダーの変数はexternとして宣言します。

extern const std::string path1;
extern const MyClass myclass1;

そしてそれらを翻訳単位で定義します。 config.cpp

const std::string path1 = "/xyz/abc";
const MyClass myclass1("test");

1つの変換ユニットからのみ使用できる定数変数が必要な場合があります。次に、ファイルスコープでその変数をstaticとして宣言できます。

static const std::string path1 = "/xyz/abc";

staticは非推奨ではなくなりました。 staticexternが暗示されることもありますが、どこでどのように行うかを常に忘れているため、通常、すべての名前空間レベルの変数に対して明示的に指定します。

12
Philipp

静的初期化の大失敗と呼ばれるものは、ある名前空間レベル変数が、以前に初期化されているかどうかに関係なく、別の名前空間レベル変数に割り当てられた値に依存している場合の問題です。 2つの例では、そのような依存関係はなく、問題はないはずです。

一方、これはそのタイプのエラーを起こしやすいです:

// header.h
extern const std::string foo;

// constant.cpp
const std::string foo( "foo" );

// main.cpp
#include "header.h"
const std::string foobar( foo+"bar" );
int main() {
   std::cout << foobar << std::endl;
}

両方が一定であっても、foofoobarの前に初期化されるという保証はありません。これは、プログラムの動作が定義されておらず、「foobar」、「bar」、またはdieを出力する可能性があることを意味します。

静的初期化の大失敗は、相互に依存するである静的変数を指します。いくつかのstatic const変数を定義するだけでは、問題の原因にはなりません。

2
Puppy