C++ 模式 : 1x base class + Nx derived classes BUT with a _last resort_ derived class

标签 c++ c++11 design-patterns

我正在尝试实现一个包含 3 级信息的记录器:一般(日期/时间)、上下文、消息

为了达到这个目标,我试图实现以下模式:

  1. 记录器类(此处不相关)
  2. 上下文类
    • 基类LoggerContext,具有生成通用级别信息的功能
    • 派生类,添加上下文特定信息(特定于应用程序的一部分)

有趣的部分开始于我尝试拥有一个none 上下文。也就是说,如果在没有上下文的情况下调用 Logger,则应使用单例 LoggerContextNone

这里是我的代码,无论我如何转换它,都无法编译:

#include <string>
#include <iostream>
#include <stdexcept>

using namespace std;

enum class LoggerArea {
        LOGGER_NONE, LOGGER_DOWNLOAD, LOGGER_COMPUTE,
};

class ELoggerContext: std::runtime_error {
        using std::runtime_error::runtime_error;
};

class LoggerContextNone; // forward declaration, only needed for 
                         // the commented version of the code

class LoggerContext {
protected:
        LoggerArea mLA;
public:
        LoggerContext(LoggerArea la);
        virtual ~LoggerContext() = 0;
        /*
        static LoggerContext& getEmptyContext() {
                static LoggerContextNone loggerContextNone = { LoggerArea::LOGGER_NONE };
                return loggerContextNone;
        }
        */
        std::string getGeneral();
        virtual std::string getContext() = 0; // pure virtual
};

string LoggerContext::getGeneral() {
        return "general informations";
}
LoggerContext::LoggerContext(LoggerArea la) :
                mLA(la) {
  if (la == LoggerArea::LOGGER_NONE) {
        throw ELoggerContext("LOGGER_NONE cannot be instantiated");
  }
}

class LoggerContextNone : LoggerContext {
private:
        LoggerContextNone() {
                mLA = LoggerArea::LOGGER_NONE;
        }
public:
        virtual ~LoggerContextNone() override {

        }
        virtual std::string getContext() override {
                return " ";
        }
        static LoggerContextNone& getInstance() {
                static LoggerContextNone instance {};
                return instance;
        }
};

int main() {
  // this should not be compilable:
  LoggerContextNone n{LoggerArea::LOGGER_NONE};
  // this should at least throw an error at run time:
  LoggerContext n{LoggerArea::LOGGER_NONE};
  return 0;
}

目标:

  • LoggerContextNone 应该派生自 LoggerContext,因为我们需要 getGeneral()
  • LoggerContextNone 不应在 getInstance 之外实例化(单例)
  • 基类不应该有空构造函数,但是派生类应该有一个
  • LoggerContextNone 不应该调用 super 构造函数,否则会抛出错误 ELoggerContext
  • LoggerContext 的任何派生类都不应覆盖 getGeneral()

在 C++ 中真的可以实现吗?我正在寻找一个干净的解决方案(没有工厂,...)

编译器的错误是:

19_virtual_class.cpp: In constructor ‘LoggerContextNone::LoggerContextNone()’:
19_virtual_class.cpp:45:22: error: no matching function for call to ‘LoggerContext::LoggerContext()’
  LoggerContextNone() {
                      ^
[...]
19_virtual_class.cpp: In function ‘int main()’:
19_virtual_class.cpp:62:46: error: no matching function for call to ‘LoggerContextNone::LoggerContextNone(<brace-enclosed initializer list>)’
   LoggerContextNone n{LoggerArea::LOGGER_NONE};
                                              ^

最后说明:这个模式在我看来在概念上很简单:许多类都派生自一个基类,另外还有一个默认类。

编辑:

如果我像@Angew 那样调整代码:

#include <string>
#include <iostream>
#include <stdexcept>

using namespace std;

enum class LoggerArea {
        LOGGER_NONE, LOGGER_DOWNLOAD, LOGGER_COMPUTE,
};

class ELoggerContext: std::runtime_error {
        using std::runtime_error::runtime_error;
};

class LoggerContextNone;

class LoggerContext {
protected:
        LoggerArea mLA;

        class LoggerContextNone_AccessToken
        {
                friend LoggerContextNone;
                LoggerContextNone_AccessToken() {}
        };
        explicit LoggerContext(LoggerContextNone_AccessToken) : mLA(LoggerArea::LOGGER_NONE) {}
public:
        LoggerContext(LoggerArea la);
        virtual ~LoggerContext() = 0;
        std::string getGeneral();
        virtual std::string getContext() = 0;
};

string LoggerContext::getGeneral() {
        string s = "general informations:";
        if (mLA==LoggerArea::LOGGER_NONE) { s += "LOGGER_NONE"; }
        else if (mLA==LoggerArea::LOGGER_DOWNLOAD) { s += "LOGGER_DOWNLOAD"; }
        else if (mLA==LoggerArea::LOGGER_COMPUTE) { s += "LOGGER_COMPUTE"; }
        else { s += "??????????"; }

        return s;
}
LoggerContext::LoggerContext(LoggerArea la) :
                mLA(la) {
  if (la == LoggerArea::LOGGER_NONE) {
        throw ELoggerContext("LOGGER_NONE cannot be instantiated");
  }
}

class LoggerContextNone : LoggerContext {
private:
        LoggerContextNone(): LoggerContext(LoggerContextNone_AccessToken()) {}
public:
        virtual ~LoggerContextNone() override {

        }
        virtual std::string getContext() override {
                return " ";
        }
        static LoggerContextNone& getInstance() {
                static LoggerContextNone instance {};
                return instance;
        }
};

class LoggerContextDerived : LoggerContext {
public:
        virtual std::string getContext() override {
                return "derived context";
        }
};

int main() {
  LoggerContextDerived n {LoggerArea::LOGGER_DOWNLOAD};

  // cout << "General : " << n.getGeneral() << endl;
  // cout << "Context : " << n.getContext() << endl;

  return 0;
}

我无法实例化派生类。 g++ 说:

9_virtual_class.cpp: In function ‘int main()’:
19_virtual_class.cpp:78:54: error: no matching function for call to ‘LoggerContextDerived::LoggerContextDerived(<brace-enclosed initializer list>)’
   LoggerContextDerived n {LoggerArea::LOGGER_DOWNLOAD};
                                                      ^

它建议我使用复制构造函数或移动构造函数。 这对我来说意味着构造函数

LoggerContext(LoggerArea la);

在派生类中不可见。

最佳答案

您可以获得想要的结果,但与您尝试过的方式不同。有问题的要求是这个:

LoggerContextNone should not call the super constructor, otherwise it would throw an error ELoggerContext

派生类总是调用基类的构造函数。在 C++ 中,如果不运行其构造函数,就不能合法地拥有类类型的有效对象。

但是,请注意它会调用基类的一个构造函数,这意味着它可以调用任意一个(由派生类决定)。因此,您可以为基类提供一个专门供 LoggerContextNone 使用的构造函数,如下所示:

class LoggerContext {
protected:
        LoggerArea mLA;
        LoggerContext() : mLA(LOGGER_NONE) {}
public:
        LoggerContext(LoggerArea la);
        virtual ~LoggerContext() = 0;
        /*
        static LoggerContext& getEmptyContext() {
                static LoggerContextNone loggerContextNone = { LoggerArea::LOGGER_NONE };
                return loggerContextNone;
        }
        */
        std::string getGeneral();
        virtual std::string getContext() = 0; // pure virtual
};

这将实现您想要的,但它将允许从 LoggerContext 派生的所有类调用该默认构造函数,如果他们选择这样做的话。如果您想避免这种情况并且使该构造函数可用于LoggerContextNone,您可以使用友元技巧和标记分派(dispatch)来做到这一点:

class LoggerContext
{
protected:
  class LoggerContextNone_AccessToken
  {
    friend LoggerContextNone;
    LoggerContextNone_AccessToken() {}
  };

  explicit LoggerContext(LoggerContextNone_AccessToken) : mLA(LOGGER_NONE) {}
protected:
  // ... the rest as before
};

LoggerContextNone::LoggerContextNone() : LoggerContext(LoggerContextNone_AccessToken())
{}

这意味着:

  1. 要调用LoggerContext的非抛出构造函数,需要传入一个LoggerContextNone_AccessToken对象。

  2. LoggerContextNone_AccessToken 有一个私有(private)构造函数,这意味着只有它的 friend 可以构造它。

  3. LoggerContextNoneLoggerContextNone_AccessToken 的唯一 friend ,因此它是唯一能够构造 LoggerContextNone_AccessToken 的类,因此也是唯一的能够调用 LoggerContext 的非抛出构造函数的类。


或者,您可以考虑是否真的需要 LoggerContextNone。也许您可以让 LoggerContext 表现得像 LoggerContextNone,并且只允许派生类提供非无行为。

关于C++ 模式 : 1x base class + Nx derived classes BUT with a _last resort_ derived class,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37269978/

相关文章:

c++ - 正则表达式匹配 Unicode 'Punctuation' 类别 c++

java - 使用 Java 中的单一模式填充表

c++ - 为什么指向函数的指针等于 1?

c++ - 为什么我们不能像在java中那样使用new ClassName(),而是使用new ClassName呢?

c++ - 自定义容器中括号运算符的常量

multithreading - C++ 11条件变量中缺少信号

java - MVP 到 MVVM Android

javascript - 创建揭示模块模式的实例

C++ OOP 基础 - 正确返回对象引用?

c++ - 如何将文本文件转换为 MPI_Bcast 可以发送的格式?