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 引用时发生(更多信息请参见:Does a const reference prolong the life of a temporary?)。

所以这里的第一件事:如果我不想使用指针,因为我在使用模板时对运行时分配不感兴趣,我想除了以上述方式复制临时创建的对象之外没有其他解决方案?

复制内容的实际问题现在来自 FileOutputter:当然不能复制一个 ofstream,那么如何复制包含流的 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 值),并且您必须检查这一点。

另一种选择是移动值,即将参数作为 r 值引用传递。这将避免复制,但是它将要求客户端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 中按值传递参数。这将允许编译器以复制省略的形式执行优化(阅读:想要速度?按值传递)。

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)) {
            // ...
}

使用上面的定义:在最好的情况下,编译器将忽略副本并且将移动构造成员(当传递临时对象时)。

In the worst case scenario: a copy and a move construction will be performed (when passing an l-value).

Using your version (passing parameters as reference to const), a copy will always be performed as the compiler can not perform optimizations.

For move construction to work, you will have to make sure that the types that are passed as parameters is move constructible (either implicitly or using a declared move ctor). If a type is not move constructible it will be copy constructed.

When it comes to copying the stream in FileOutputter, using std::shared_ptr seems like a good solution, although you should initialize mStream in the initialization list instead of assigning in the ctor body:

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.

A std::ofstream is non-copyable and passing around a smart pointer (make sure to use std::shared_ptr though) is probably the simplest solution in your case and also in my opinion, contrary to what you say, not overy complex.

Another approach would be to make the stream member static, but then every instance of FileOutputter would use the same std::ofstream object and it would not be possible to use parallel logger objects writing to different files etc.

Alternatively you could move the stream as std::ofstream is non-copyable but movable. This would however require you to make FileOutputter movable and non-copyable (and probably LoggerImpl as well), as using a "moved" object, other than its dtor, can result in UB. Making an object that manages move-only types to itself become move-only may make a lot of sense sometimes though.

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.

Also, in the example provided, you don't need to declare a copy ctor or a dtor for FileOutputter, as they will be implicitly generated by the compiler.

于 2013-12-02T15:09:12.023 回答
3

您可以让策略类包含静态函数,因此理想情况下您希望 FileOutputter 看起来像:

    template<std::string fileName>
    struct FileOutputter {

        static void out(const char* msg) const {
            std::ofstream out(fileName);
            out << msg;
        }
    };

您将像这样创建 LoggerImpl 的实例

LoggerImpl<NoFilter, SimpleFormatter, FileOutputter<"log.txt"> > FileLogger;

这意味着您的 LoggerImpl 不需要存储策略类的副本,它只需要调用它们的静态函数。不幸的是,这不起作用,因为您不能将字符串作为模板参数,但您可以构建一个字符串表并在字符串表中传递文件名的索引。因此,理想情况下,您再次希望它看起来像:

//This class should be a singleton
class StringManager
{
  std::vector<std::string> m_string;
public:
  void addString(const std::string &str);
  const std::string &getString(int index);
  int getIndexOf(const std::string &str);
};

然后您的 FileLogger 将获得一个 int 作为模板参数,它将是 StringManager 中字符串的索引。这也不太有效,因为您需要在编译时可用的索引,并且 StringManager 将在运行时初始化。因此,您必须手动构建字符串表并手动写入字符串的索引。所以你的代码看起来像(在你让 StringManager 成为单例之后:

StringManager::instance()->addString("a");
StringManager::instance()->addString("b");
StringManager::instance()->addString("c");
StringManager::instance()->addString("d");
StringManager::instance()->addString("log.txt");
LoggerImpl<NoFilter, SimpleFormatter, FileOutputter<4> > FileLogger;

您必须确保在创建 FileLogger 的第一个实例之前完全初始化 StringManager。这有点难看,但使用带有字符串的模板有点难看。您还可以执行以下操作:

template <class FilenameHolder>
struct FileOutputter {

        static void out(const char* msg) const {
            std::ofstream out(FilenameHolder::fileName());
            out << msg;
        }
    };

class MyFileNameHolder
{
  static std::string fileName() {return "log.txt";}
};
于 2013-12-02T08:17:01.247 回答