2

この投稿は、最後の短い質問では長すぎるように思われるかもしれません。ただし、思いついたデザインパターンについても説明する必要があります。多分それは一般的に使用されていますが、私はそれを見たことがありません(または多分それは単に機能しません:)。

まず、これが(私の理解では)「静的初期化順序の大失敗」のために未定義の動作をするコードです。問題は、Spanish::s_englishToSpanishの初期化がEnglish::s_numberToStrに依存していることです。これは静的に初期化され、異なるファイルにあるため、これらの初期化の順序は未定義です。

ファイル:English.h

#pragma once

#include <vector>
#include <string>

using namespace std;

struct English {
    static vector<string>* s_numberToStr;
    string m_str;

    explicit English(int number)
    {
        m_str = (*s_numberToStr)[number];
    }
};

ファイル:English.cpp

#include "English.h"

vector<string>* English::s_numberToStr = new vector<string>( /*split*/
[]() -> vector<string>
{
    vector<string> numberToStr;
    numberToStr.push_back("zero");
    numberToStr.push_back("one");
    numberToStr.push_back("two");
    return numberToStr;
}());

ファイル:Spanish.h

#pragma once

#include <map>
#include <string>

#include "English.h"

using namespace std;

typedef map<string, string> MapType;

struct Spanish {
    static MapType* s_englishToSpanish;
    string m_str;

    explicit Spanish(const English& english)
    {
        m_str = (*s_englishToSpanish)[english.m_str];
    }
};

ファイル:Spanish.cpp

#include "Spanish.h"

MapType* Spanish::s_englishToSpanish = new MapType( /*split*/
[]() -> MapType
{
    MapType englishToSpanish;
    englishToSpanish[ English(0).m_str ] = "cero";
    englishToSpanish[ English(1).m_str ] = "uno";
    englishToSpanish[ English(2).m_str ] = "dos";
    return englishToSpanish;
}());

ファイル:StaticFiasco.h

#include <stdio.h>
#include <tchar.h>
#include <conio.h>

#include "Spanish.h"

int _tmain(int argc, _TCHAR* argv[])
{
    _cprintf( Spanish(English(1)).m_str.c_str() ); // may print "uno" or crash

    _getch();
    return 0;
}

静的初期化順序の問題を解決するために、最初に使用するときに構築するイディオムを使用し、これらの静的初期化を次のように関数ローカルにします。

ファイル:English.h

#pragma once

#include <vector>
#include <string>

using namespace std;

struct English {
    string m_str;

    explicit English(int number)
    {
        static vector<string>* numberToStr = new vector<string>( /*split*/
        []() -> vector<string>
        {
            vector<string> numberToStr_;
            numberToStr_.push_back("zero");
            numberToStr_.push_back("one");
            numberToStr_.push_back("two");
            return numberToStr_;
        }());

        m_str = (*numberToStr)[number];
    }
};

ファイル:Spanish.h

#pragma once

#include <map>
#include <string>

#include "English.h"

using namespace std;

struct Spanish {
    string m_str;

    explicit Spanish(const English& english)
    {
        typedef map<string, string> MapT;

        static MapT* englishToSpanish = new MapT( /*split*/
        []() -> MapT
        {
            MapT englishToSpanish_;
            englishToSpanish_[ English(0).m_str ] = "cero";
            englishToSpanish_[ English(1).m_str ] = "uno";
            englishToSpanish_[ English(2).m_str ] = "dos";
            return englishToSpanish_;
        }());

        m_str = (*englishToSpanish)[english.m_str];
    }
};

しかし今、私たちは別の問題を抱えています。関数ローカルの静的データのため、これらのクラスはどちらもスレッドセーフではありません。これを解決するために、両方のクラスに静的メンバー変数とその初期化関数を追加します。次に、この関数内で、関数ローカル静的データを持つ各関数を1回呼び出すことにより、すべての関数ローカル静的データの初期化を強制します。したがって、事実上、プログラムの開始時にすべてを初期化していますが、それでも初期化の順序を制御しています。これで、クラスはスレッドセーフになるはずです。

ファイル:English.h

#pragma once

#include <vector>
#include <string>

using namespace std;

struct English {
    static bool s_areStaticsInitialized;
    string m_str;

    explicit English(int number)
    {
        static vector<string>* numberToStr = new vector<string>( /*split*/
        []() -> vector<string>
        {
            vector<string> numberToStr_;
            numberToStr_.push_back("zero");
            numberToStr_.push_back("one");
            numberToStr_.push_back("two");
            return numberToStr_;
        }());

        m_str = (*numberToStr)[number];
    }

    static bool initializeStatics()
    {
        // Call every member function that has local static data in it:
        English english(0); // Could the compiler ignore this line?
        return true;
    }
};
bool English::s_areStaticsInitialized = initializeStatics();

ファイル:Spanish.h

#pragma once

#include <map>
#include <string>

#include "English.h"

using namespace std;

struct Spanish {
    static bool s_areStaticsInitialized;
    string m_str;

    explicit Spanish(const English& english)
    {
        typedef map<string, string> MapT;

        static MapT* englishToSpanish = new MapT( /*split*/
        []() -> MapT
        {
            MapT englishToSpanish_;
            englishToSpanish_[ English(0).m_str ] = "cero";
            englishToSpanish_[ English(1).m_str ] = "uno";
            englishToSpanish_[ English(2).m_str ] = "dos";
            return englishToSpanish_;
        }());

        m_str = (*englishToSpanish)[english.m_str];
    }

    static bool initializeStatics()
    {
        // Call every member function that has local static data in it:
        Spanish spanish( English(0) ); // Could the compiler ignore this line?
        return true;
    }
};

bool Spanish::s_areStaticsInitialized = initializeStatics();

そして、ここに質問があります:一部のコンパイラが、ローカルの静的データを持つ関数(この場合はコンストラクタ)への呼び出しを最適化する可能性はありますか?したがって、問題は「副作用がある」と正確に何を意味するのかということです。これは、私の理解では、コンパイラがそれを最適化することを許可されていないことを意味します。コンパイラに関数呼び出しを無視できないと思わせるのに十分な関数ローカル静的データがありますか?

4

4 に答える 4

1

C++11標準のセクション1.9「プログラム実行」[intro.execution]は次のように述べています。

1この国際規格のセマンティック記述は、パラメーター化された非決定論的抽象マシンを定義します。...以下で説明するように、抽象マシンの観察可能な動作を(のみ)エミュレートするには、準拠する実装が必要です。
..。

5整形式プログラムを実行する適合実装は、同じプログラムと同じ入力を持つ抽象マシンの対応するインスタンスの可能な実行の1つと同じ観察可能な動作を生成するものとします。
..。

8準拠する実装の最小要件は次のとおりです
。—揮発​​性オブジェクトへのアクセスは、抽象マシンのルールに従って厳密に評価されます。
—プログラムの終了時に、ファイルに書き込まれるすべてのデータは、抽象セマンティクスに従ってプログラムを実行すると生成される可能性のある結果の1つと同一である必要があります。
—対話型デバイスの入力と出力のダイナミクスは、プログラムが入力を待機する前に、プロンプト出力が実際に配信されるように行われる必要があります。インタラクティブデバイスを構成するものは、実装によって定義されます。
これらをまとめて、プログラムの観察可能な動作と呼びます。
..。

12 volatile glvalue(3.10)で指定されたオブジェクトへのアクセス、オブジェクトの変更、ライブラリI / O関数の呼び出し、またはこれらの操作のいずれかを実行する関数の呼び出しはすべて副作用であり、実行環境の状態の変化です。 。

また、3.7.2「自動保存期間」[basic.stc.auto]では、

3自動保存期間の変数に初期化または副作用のあるデストラクタがある場合、そのブロックが終了する前に破棄したり、クラスオブジェクトを除いて、使用されていないように見えても最適化として削除したりすることはできません。または、12.8で指定されているように、そのコピー/移動を削除できます。

12.8-31は、ここでは無関係であると私が信じるコピーの省略について説明しています。

したがって、問題は、ローカル変数の初期化に、最適化を妨げる副作用があるかどうかです。動的オブジェクトのアドレスを使用して静的変数の初期化を実行できるため、十分な副作用(オブジェクトの変更など)が発生すると思います。また、揮発性オブジェクトを使用した操作をそこに追加して、排除できない観察可能な動作を導入することもできます。

于 2012-01-18T22:31:31.860 に答える
1

English::s_numberToStr を public static 関数の後ろに隠して、コンストラクター構文を完全にスキップしてみませんか? DCLPを使用してスレッドセーフを確保します。

初期化に重大な副作用が伴うクラスの静的変数を避けることを強くお勧めします。一般的な設計パターンとして、それらは解決するよりも多くの問題を引き起こす傾向があります。ここで懸念しているパフォーマンスの問題が何であれ、正当化する必要があります。なぜなら、それらが実際の状況下で測定可能かどうかは疑わしいからです。

于 2012-01-18T16:56:33.490 に答える
1

わかりました、一言で言えば:

  1. クラスの静的メンバーを公開する必要がある理由がわかりません-それらは実装の詳細です。

  2. それらを非公開にしないで、代わりにコンパイル ユニットのメンバーにします (クラスを実装するコードがある場所)。

  3. boost::call_once静的初期化を実行するために使用します。

最初の使用時の初期化は、順序を強制するのが比較的簡単ですが、順序どおりに実行するのがはるかに難しいのは破棄です。ただし、call_once で使用される関数は例外をスローしてはならないことに注意してください。したがって、失敗する可能性がある場合は、何らかの失敗状態を残して、呼び出し後にそれを確認する必要があります。

(あなたの実際の例では、負荷はハードコードされたものではなく、ある種の動的テーブルをロードする可能性が高いため、メモリ内配列を作成することはできません)。

于 2012-01-11T15:53:15.660 に答える
0

初期化順序を制御するために追加の作業が必要になる場合があります。お気に入り、

class staticObjects
{
    private:
    vector<string>* English::s_numberToStr;
    MapType* s_englishToSpanish;
};

static staticObjects objects = new staticObjects();

次に、それを取得するためのいくつかのインターフェースを定義します。

于 2012-01-17T15:00:26.560 に答える