6

ポリシーベースの設計に関する記事を読み、自分で何かを試してみたいと思った後、以前行ったロガークラスをポリシーベースのアプローチに再設計することに時間を費やしています。

いくつかのコード:

template <class Filter, class Formatter, class Outputter>
    class LoggerImpl : public LoggerBase {
    public:
        LoggerImpl(const Filter& filter = Filter(), const Formatter& formatter = Formatter(), const Outputter& outputter = Outputter());
        ~LoggerImpl();

        void log(int channel, int loglevel, const char* msg, va_list list) const;
    private:
        const Filter mFilter;
        const Formatter mFormatter;
        const Outputter mOutputter;
    };

template <class Filter, class Formatter, class Outputter>
LoggerImpl<Filter, Formatter, Outputter>::LoggerImpl(const Filter& filter, const Formatter& formatter, const Outputter& outputter) : 
            mFilter(filter), mFormatter(formatter), mOutputter(outputter) {
                debuglib::logdispatch::LoggerMgr.addLogger(this);
        }

typedef LoggerImpl<NoFilter, SimpleFormatter, ConsoleOutputter> ConsoleLogger;
typedef LoggerImpl<ChannelFilter, SimpleFormatter, VSOutputter> SimpleChannelVSLogger;
typedef LoggerImpl<NoFilter, SimpleFormatter, FileOutputter> FileLogger;

ConsoleLogger c;
SimpleChannelVSLogger a(const ChannelFilter(1));
FileLogger f(NoFilter(), SimpleFormatter(), FileOutputter("log.txt"));

// macro for sending log message to all current created loggers
LOG(1, WARN, "Test %d", 1423);

ロガーによっては、SimpleChannelVsLogger 内のログチャネルや FileOututter 内のログファイルのファイル名などの追加情報を渡す必要があります。

パラメーターを LoggerImpl のコンストラクターに const 参照として渡し、後でロガー クラスに格納されているオブジェクトにコピーします。一時的に作成されたオブジェクトを const 参照にバインドするときに発生する関数引数を介して有効期間の延長が推移的ではないため、それらをコピーする必要があります (詳細はこちら: Does a const reference prolong the life of a temporary? )。

ここで最初に: テンプレートを使用するときにランタイム割り当てに興味がないのでポインターを使用したくない場合は、一時的に作成されたオブジェクトを上記のようにコピーする以外に解決策はないと思いますか?

コピーの実際の問題は、FileOutputter に付属しています。もちろん、ストリームをコピーすることはできません。ストリームを含む FileOutputter オブジェクトをコピーするにはどうすればよいでしょうか。この問題を克服するために、次の解決策を思い付きました。

struct FileOutputter {
            // c_tor
            FileOutputter() {}

            // c_tor
            explicit FileOutputter(const char* fname) {
                mStream = std::make_shared<std::fstream>(fname, std::fstream::out);
            }

            // The copy c_tor will be invoked while creating any logger with FileOutputter
            // FileLogger f(NoFilter(), SimpleFormatter(), FileOutputter("log.txt"));
            // as all incoming paramters from the constructors stack frame are copied into the current instance of the logger
            // as copying a file-stream is not permitted and not good under any means 
            // a shared pointer is used in the copy c_tor 
            // to keep the original stream until no reference is referring to it anymore
            FileOutputter(const FileOutputter& other)  {
                mStream = other.mStream;
            }

            ~FileOutputter() {
            }

            void out(const char* msg) const {
                *mStream << msg;
            }

            std::shared_ptr<std::fstream> mStream;
        };

どういうわけか、これは「単純なロガークラス」にとっては少し複雑に思えるかもしれませんが、これは、この場合のポリシーベースの設計アプローチの「問題」にすぎない可能性があります。

どんな考えでも大歓迎です

4

2 に答える 2

4

オブジェクトをクラスのメンバーとして保存する場合は、オブジェクトをコピーする必要があります。

一時オブジェクトをパラメーターとして ctor に渡すことができるため、参照の保存は危険です。これにより、一時オブジェクトが破棄されたときにダングリング参照が発生します。

パラメーターをポインターとして渡すことも代替手段ですが、nullptr(NULL 値) を渡すことが可能になるため、このアプローチにも問題があり、これを確認する必要があります。

もう 1 つの方法は、値を移動することです。つまり、パラメータを右辺値参照として渡します。std::moveこれによりコピーは回避されますが、クライアントは ctor を呼び出すときに一時オブジェクトまたはオブジェクトを渡す必要があります。左辺値参照を渡すことはできなくなります。

// Define ctor as taking r-value references.
template <class Filter, class Formatter, class Outputter>
LoggerImpl<Filter, Formatter, Outputter>::LoggerImpl(Filter&& filter, Formatter&& formatter, Outputter&& outputter) : 
        mFilter(std::move(filter)), mFormatter(std::move(formatter)), mOutputter(std::move(outputter)) {
            // ...
}

/* ... */

// Calling ctor.
FileLogger f1(NoFilter(), SimpleFormatter(), FileOutputter("log.txt")); // OK, temporaries.
FileOutputter fout("log.txt");
FileLogger f2(NoFilter(), SimpleFormatter(), fout); // Illegal, fout is l-value.
FileLogger f3(NoFilter(), SimpleFormatter(), std::move(fout)); // OK, passing r-value. fout may not be used after this!

コピー アプローチを使用することにした場合は、代わりに ctor でパラメーターを値で渡すことをお勧めします。これにより、コンパイラはコピー省略として最適化を実行できます(読み取り: Want Speed? Pass by Value )。

template <class Filter, class Formatter, class Outputter>
LoggerImpl<Filter, Formatter, Outputter>::LoggerImpl(Filter filter, Formatter formatter, Outputter outputter) : 
        mFilter(std::move(filter)), mFormatter(std::move(formatter)), mOutputter(std::move(outputter)) {
            // ...
}

上記の定義を使用すると、最良のシナリオでは、コンパイラはコピーを省略し、メンバーは移動構築されます(一時オブジェクトを渡す場合)。

最悪のシナリオでは、コピーと移動の構築が実行されます (左辺値を渡す場合)。

バージョンを使用して (パラメータを const への参照として渡す)、コンパイラは最適化を実行できないため、常にコピーが実行されます。

移動構築を機能させるには、パラメータとして渡される型が移動構築可能であることを確認する必要があります (暗黙的に、または宣言された移動 ctor を使用して)。型が移動構築可能でない場合は、コピー構築されます。

でストリームをコピーする場合は、ctor 本体で代入するのではなく、初期化リストFileOutputterで初期化する必要がありますが、使用std::shared_ptrは良い解決策のように思えます。mStream

explicit FileOutputter(const char* fname)
    : mStream(std::make_shared<std::ofstream>(fname)) {}

// Note: Use std::ofstream for writing (it has the out-flag enabled by default).
//       There is another flag that may be of interest: std::ios::app that forces
//       all output to be appended at the end of the file. Without this, the file
//       will be cleared of all contents when it is opened.

Astd::ofstreamはコピー不可であり、スマート ポインターを渡すこと (必ず使用std::shared_ptrしてください) は、おそらくあなたのケースで最も簡単な解決策であり、私の意見では、あなたの言うこととは反対に、過度に複雑ではありません

別のアプローチは、ストリーム メンバーを静的にすることですが、その場合、すべてのインスタンスがFileOutputter同じstd::ofstreamオブジェクトを使用し、異なるファイルなどに書き込む並列ロガー オブジェクトを使用することはできなくなります。

または、コピー不可で移動可能としてストリームを移動することもできます。ただし、dtor 以外の「移動された」オブジェクトを使用すると UB が発生する可能性があるため、これには移動可能でコピー不可にする必要があります(おそらく同様に)。ただし、移動専用型をそれ自体に管理するオブジェクトを移動専用にすることは、場合によっては非常に理にかなっている場合があります。std::ofstreamFileOutputterLoggerImpl

std::ofstream out{"log.txt"};
std::ofstream out2{std::move(out)} // OK, std::ofstream is moveable.
out2 << "Writing to stream";       // Ok.
out << "Writing to stream";        // Illegal, out has had its guts ripped out.

FileOutputterまた、提供されている例では、コンパイラによって暗黙的に生成されるため、 のコピー ctor または dtor を宣言する必要はありません。

于 2013-12-02T15:09:12.023 に答える