0

私はオブジェクトと関数のライブラリを開発しており、super.hppいくつかの初期化タスクを含むヘッダー ファイル (ここでは という名前) を持っています。

スーパー.hpp

#ifndef H_INIT
#define H_INIT

#include <iostream>
#include <string>

static bool isInit = false;

struct settings_struct{
    std::string path = "foo";
    void load(){ path = "bar"; }
};

struct initializer_struct{
    settings_struct settings;

    initializer_struct(){
        if(!isInit){
            std::cout << "Doing initialization\n";
            settings.load();
            isInit = true;
        }
        // settings.load();
    }//====================

    ~initializer_struct(){
        if(isInit){
            std::cout << "Doing closing ops\n";
            isInit = false;
        }
    }
};

static initializer_struct init; // static declaration: only create one!

#endif

このヘッダーの意図は、initializer_structオブジェクトを一度作成することです。この構造体が構築されると、ライブラリ全体のフラグと設定を設定するいくつかのアクションが実行されます。これらのアクションの 1 つは、XML ファイルから設定をロードする設定構造体の作成です。このアクションは、init 構造体が構築されるときに 1 回だけ発生する必要があるため、変数 (ここではpath) は設定ファイルから保存されます。super.hppさまざまなオブジェクトがさまざまな容量で使用されるため、ヘッダーはライブラリ内のすべてのオブジェクトに含まれています。つまり、アプリケーションでどのオブジェクトが使用されるかを予測する方法がないため、no と呼ばれることを保証するために、それらすべてにヘッダーを含めますsuper.hpp。どのオブジェクトが使用されているかは重要です。

私の問題は次のとおりです。super.hppメインアプリケーションによってすべてロードされる複数のクラス/オブジェクトに含めると、構造体initが再初期化されたように見え、settings_struct構築時に設定された変数がデフォルト値で上書きされます。これを実際に確認するには、次の追加ファイルを検討してください。

test.cpp

#include "classA.hpp"
#include "classB.hpp"
#include <iostream>

int main(int argc, char *argv[]){
    (void) argc;
    (void) argv;

    classA a;
    classB b;

    std::cout << "Settings path = " << init.settings.path << std::endl;
    std::cout << "Class A Number = " << a.getNumber() << std::endl;
    std::cout << "Class B Number = " << b.getInteger() << std::endl;
}

classA.hpp

#ifndef H_CLASSA
#define H_CLASSA

class classA{
private:
    double number;

public:
    classA() : number(7) {}
    double getNumber();
};

#endif

classA.cpp

#include "super.hpp"
#include "classA.hpp"

double classA::getNumber(){ return number; }

classB.hpp

#ifndef H_CLASSB
#define H_CLASSB

class classB{
private:
    int number;

public:
    classB() : number(3) {}
    int getInteger();
};

#endif

classB.cpp

#include "super.hpp"
#include "classB.hpp"

int classB::getInteger(){ return number; }

サンプルをコンパイルして実行するには、

g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic -c classA.cpp -o classA.o
g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic -c classB.cpp -o classB.o
g++ -std=c++11 -W -Wall -Wextra -Weffc++ -pedantic classA.o classB.o test.cpp -o test.out
./test.out

test.out からの出力は次のようになると思います。

Doing initialization
Settings path = bar
Number = 7
Doing closing ops

ただし、これを実行すると、代わりに「Settings path = foo」が表示されます。したがって、私の結論は、initializer_structinit、が複数回構築されているということです。最初はブール値isInitが false で、設定構造load関数pathが「bar」に設定されます。以降のすべての初期化でisInitは は true であるため、関数は再度呼び出されず、初期化されていない(つまり)loadからの変数値が以前に読み込まれた値を上書きするように見えるため、inの出力になります。settingspath = "foo"init.settings.pathtest.cpp

どうしてこれなの?initヘッダーが含まれるたびにオブジェクトが構築されるのはなぜですか? インクルード ガードは、ヘッダー コードが複数回呼び出されるのを防ぐと考えていたでしょう。非静的変数で変数を作成すると、複数のコピーが作成され、出力には「初期化の実行」と「終了操作の実行」の複数の反復が出力されinitます。さらに、コンストラクターの条件ステートメントの外側で関数呼び出しのtest.hppコメントを外すと、出力に「bar」の設定パスが表示されます。最後に、fromのインクルードを削除すると、"bar" のパス値が得られます。これは、複数の のインクルージョンが複数のコンストラクター呼び出しにつながるという私の仮説をさらに裏付けています。settings.load()initializer_struct()super.hppclassA.cpptest.hpp

settings.load()' called for every object that includessuper.hpp`は避けたいので、コマンドを条件ステートメント内に配置しました。何かご意見は?設定ファイルが 1 回だけ読み込まれ、読み込まれた値が上書きされないようにするにはどうすればよいですか? これは、ライブラリが使用するいくつかのフラグと設定を設定するための完全に鈍い方法ですか? もしそうなら、プロセスをよりシンプルかつ/またはよりエレガントにするための提案はありますか?

ありがとう!

編集:より複雑な設定をより正確に表すために、2 つのオブジェクト クラスを含めるように更新されました。

4

3 に答える 3

2

ヘッダー ファイルで、次のstaticグローバル オブジェクトを定義します。

static bool isInit = false;

static initializer_struct init;

これらのstaticグローバル オブジェクトは、このヘッダー ファイルを含むすべての翻訳単位でインスタンス化されます。すべての翻訳単位にこれら 2 つのオブジェクトのコピーがあります。

initializer_struct(){

ただし、このコンストラクターは、アプリケーションで一度だけ定義されます。コンパイラは、これらのヘッダー ファイルを含むすべての翻訳単位でコンストラクターを実際にコンパイルし、すべての翻訳単位で、コンストラクターはstaticその翻訳単位からグローバル オブジェクトを使用します。

ただし、アプリケーションをリンクすると、すべての翻訳単位で重複するコンストラクターが削除され、コンストラクターの 1 つのインスタンスのみが最終的な実行可能ファイルの一部になります。どの重複インスタンスが削除されるかは指定されていません。リンカーはそれらの 1 つを選択し、それが幸運な勝者です。staticコンストラクターのどのインスタンスが残っていても、独自の翻訳単位からのグローバル オブジェクトのみを使用します。

これを正しく行う方法があります。代わりに、staticグローバル オブジェクトをstaticクラス メンバーとして宣言し、それらのstaticグローバル オブジェクトを翻訳単位の 1 つでインスタンス化します。あなたの翻訳ユニットmain()は素晴らしい選択です。次に、すべての単一のコピーが作成されます。

于 2016-04-21T02:32:35.653 に答える
0

Varshavchik の提案に従って、いくつかの変更を加えました。まず、super.hppヘッダーを、ライブラリ内のすべてのオブジェクトを拡張できる非常に基本的なクラスに置き換えました。

base.hpp

#ifndef H_BASE
#define H_BASE

#include <iostream>
#include <string>

struct settings_struct{
    settings_struct(){
        std::cout << "Constructing settings_struct\n";
    }
    std::string path = "foo";
    void load(){ path = "bar"; }
};

struct initializer_struct{
    settings_struct settings;

    initializer_struct(){
        std::cout << "Constructing initializer_struct\n";
    }

    ~initializer_struct(){
        std::cout << "Doing closing ops\n";
    }

    void initialize(){
        std::cout << "Doing initialization\n";
        settings.load();
    }
};

class base{
public:
    static initializer_struct init;
    static bool isInit;

    base();
};

#endif

base.cpp

#include "base.hpp"

initializer_struct base::init;
bool base::isInit = false;

base::base(){
    if(!isInit){
        init.initialize();
        isInit = true;
    }
}

他のファイルは、いくつかの変更を除いて、ほぼ同じままです。まず、クラスclassAclassB拡張します。base

class classA : public base {...}
class classB : public base {...}

これで、いずれかのオブジェクトが構築されると、基本クラスのコンストラクターが呼び出され、設定やその他の変数が一度初期化されます。isInitとは両方ともクラスinitの静的メンバーであるため、ヘッダーを含むか、オブジェクトを拡張するすべてのオブジェクトは、私のニーズに合った値にアクセスできます。これらの値には、basebasebase

base::init.settings.path

Settings path = bar出力は、「foo」の代わりに、私が期待し、望んでいるものになりました

于 2016-04-21T14:49:17.263 に答える
0

静的 isInit をクラスの静的メンバーに移動し、init のインスタンス化を翻訳単位に移動するだけです。結果のファイルは次のようになります。

スーパー.hpp

#ifndef H_INIT
#define H_INIT

#include <iostream>
#include <string>

struct initializer_struct{
    static bool isInit;

    struct settings_struct{
        std::string path = "foo";
        void load(){ path = "bar"; }
    } settings;

    initializer_struct(){
        if(!isInit){
            std::cout << "Doing initialization\n";
            settings.load();
            isInit = true;
        }
        // settings.load();
    }//====================

    ~initializer_struct(){
        if(isInit){
            std::cout << "Doing closing ops\n";
            isInit = false;
        }
    }
};

extern initializer_struct init; // extern declaration, instantiate it in super.cpp

#endif

super.cpp

#include "super.hpp"

bool initializer_struct::isInit = false;
initializer_struct init;

ただし、シングルトン パターンを使用する方がよいでしょう。シングルトン パターンでは、クラスのインスタンスが 1 つだけインスタンス化されるようにします。ここでいくつかの情報を得ることができます: C++ シングルトン デザイン パターン

次のようになります。

singleton.hpp

#pragma once

class initializer_struct{
public:
    struct settings_struct{
        std::string path = "foo";
        void load(){ path = "bar"; }
    } settings;

    static initializer_struct *GetInstance() {
        if (_instance == NULL) {
            _instance = new initializer_struct();
        }
        return _instance;
    }
    ~initializer_struct(){
    }    
private:
    initializer_struct(){
        if(!isInit){
            std::cout << "Doing initialization\n";
            settings.load();
            isInit = true;
        }
    }

    static initializer_struct *_instance;
}

シングルトン.cpp

#include "singleton.hpp"

initializer_struct *initializer_struct::_instance = NULL;

_instance をポインターから単なるオブジェクトに変更し、singleton.cpp でオブジェクトとして宣言し、GetInstance() プロトタイプを次のように変更することで、ロード時の初期化を行うこともできます。

initializer_struct &GetInstance() { return _instance; }

ただし、後者では、静的初期化順序の大失敗に注意してください ( http://yosefk.com/c++fqa/ctors.html#fqa-10.12 )。つまり、どちらが最初に初期化されるかわからないため、クラスの初期化が別のクラスの初期化に依存しない場合は、後者のアプローチを実行できます。

于 2016-04-21T15:13:01.113 に答える