4

エンティティ ベースのコンポーネント システムを開発しており、特定のインデックスをコンポーネント タイプに割り当てようとしています。

static std::size_t getNextTypeId() {
    static std::size_t lastTypeIdBitIdx{0};
    ++lastTypeIdBitIdx;

    // This line produces the output at the end of the question
    std::cout << lastTypeIdBitIdx << std::endl; 

    return lastTypeIdBitIdx;
}

// I'm assuming that TypeIdStorage<T1>::bitIdx will always be different
// from TypeIdStorage<T2>::bitIdx
template<typename T> struct TypeIdStorage { 
    static const std::size_t bitIdx; 
};

// This line statically initializes bitIdx, getting the next id
template<typename T> const std::size_t TypeIdStorage<T>::bitIdx{getNextTypeId()};

私のゲーム コードでは、次のように宣言された約 20 のコンポーネント タイプがあります。

struct CPhysics : public sses::Component { ... };
struct CHealth : public sses::Component { ... };
struct CWeapon : public sses::Component { ... };
// and so on...

私のエンティティシステムコードでは、コンポーネントタイプの1つであることを数回使用TypeIdStorage<T>::bitIdxTています-これが起こることを期待しています:

  • 存在する場合TypeIdStorage<T>は、単純に を返しTypeIdStorage<T>::bitIdxます。
  • 存在しない場合は、作成して で初期化bitIdxgetNextTypeId()ます。

これは、アプリケーションを実行したときに出力されるものです。

1 2 3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ...

getNextTypeId()呼び出しで同じ番号が返される可能性はありますか? この種の出力は不可能なはずです。

静的変数が繰り返されずにインクリメントされることが保証されていませんか? 私はここで本当に混乱しています。


デバッグモードとリリースモードの両方で、 g++ 4.8.1clang++ 3.4の両方でテスト済み。同じ出力。

valgrindは興味深いものを出力しません。

clang++ のAddressSanitizerも興味深いものを出力しません。

プログラムのエントリ ポイントを に設定するとint main() { return 0; }、まったく同じ出力が生成されます。問題はコンパイル時ですが、どうすれば可能ですか? これは私には不可能な状況のように思えます。

4

2 に答える 2

4

static関数を宣言するときにを削除する必要があります。

std::size_t getNextTypeId() {
    // ...
}

この関数のバージョンが 1 つだけ存在することを確認します。このためには、おそらく定義を実装ファイルに移動し、宣言のみをヘッダーに残す必要もあります。

function を宣言staticすると、シンボルはエクスポートされず、同じ翻訳単位でのみ使用できることを意味します。翻訳単位間で共有されなくなりました。これにより、各翻訳単位は関数の独自のコピーを持ち、各コピーはもちろん独自のカウンターを持ちます。

于 2013-10-13T12:15:21.630 に答える
3

問題を再現するのに十分なコードを投稿していません。ただし、ヘッダー ファイルに上記のコードがあり、それを複数の翻訳単位から使用すると、観測された動作を得ることができます。この場合のコードの問題は、同じテンプレート コードが異なる関数、つまり異なるバージョンの を使用することに解決されることですgetNextTypeId()。もちろん、この問題の解決策は、関数にするのではなく、すべての場合で同じ関数を使用することです。たとえば、それを にします。例えば:getNextTypeId()staticinline

  1. ヘッダー ファイル ( にあると想定dcount.h):

    #include <iostream>
    
    static std::size_t getNextTypeId() {
        static std::size_t lastTypeIdBitIdx{0};
        ++lastTypeIdBitIdx;
    
        // This line produces the output at the end of the question
        std::cout << "last index=" << lastTypeIdBitIdx << '\n';
    
        return lastTypeIdBitIdx;
    }
    
    // I'm assuming that TypeIdStorage<T1>::bitIdx will always be different
    // from TypeIdStorage<T2>::bitIdx
    template<typename T> struct TypeIdStorage { 
        static const std::size_t bitIdx; 
    };
    
    // This line statically initializes bitIdx, getting the next id
    template<typename T> const std::size_t TypeIdStorage<T>::bitIdx{getNextTypeId()};
    
  2. 最初の翻訳単位 ( にあると仮定dcount-t1.cpp):

    #include "dcount.h"
    
    struct A {};
    struct B {};
    struct C {};
    
    int f()
    {
        TypeIdStorage<A>::bitIdx;
        TypeIdStorage<B>::bitIdx;
        TypeIdStorage<C>::bitIdx;
    }
    
  3. 2 番目の翻訳単位 ( にあると仮定dcount-t2.cpp):

    #include "dcount.h"
    
    struct D {};
    struct E {};
    struct F {};
    
    int g()
    {
        TypeIdStorage<D>::bitIdx;
        TypeIdStorage<E>::bitIdx;
        TypeIdStorage<F>::bitIdx;
    }
    
  4. 最後に、これらをまとめたプログラム ( dcount-main.cpp):

    extern void f();
    extern void g();
    
    int main()
    {
        f();
        g();
    }
    

たとえば、これらのファイルをコンパイルすると、g++ -std=c++11 -o dcount dcount-t1.cpp dcount-t2.cpp dcount-main.cpp気付いた動作を再現する実行可能ファイルが生成されます。

$ g++ -std=c++11 -o dcount dcount-t1.cpp dcount-t2.cpp dcount-main.cpp 
$ ./dcount 
last index=1
last index=2
last index=3
last index=1
last index=2
last index=3
于 2013-10-13T12:14:54.630 に答える