C++模式:1x基类+Nx派生类,但具有_上一个资源派生类

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

本文关键字:派生 上一个 资源 基类 1x +Nx 模式 C++      更新时间:2023-10-16

我正在尝试实现一个具有三个信息级别的记录器:常规(日期/时间)、上下文、消息

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

  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(singleton)外部实例化
  • 基类不应该有空构造函数,但派生类应该有一个
  • LoggerContextNone不应该调用超级构造函数,否则会抛出错误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不应该调用超级构造函数,否则会抛出错误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,您可以使用友谊技巧和标记调度来做到这一点:

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有一个私有构造函数,这意味着只有它的朋友才能构造它

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


或者,您可以考虑是否真的需要LoggerContextNone。也许您可以让LoggerContext的行为与LoggerContextNone一样,只允许派生类提供non-none行为。

@Angew的答案对于使模式工作至关重要,但代码中还有许多(而且仍然存在一些)其他问题。

这是我能做的最好的事情,它仍然不起作用,但也许在接下来的几天里,我会100%得到它:

#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() {};
        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() {
        // this was:
        // static LoggerContextNone& getInstance() {
                static LoggerContextNone instance {};
                return &instance;
        }
};
class LoggerContextDerived : public LoggerContext {
public:
        LoggerContextDerived(LoggerArea la):LoggerContext(la) {  };
        virtual std::string getContext() override {
                return "derived context";
        }
        virtual ~LoggerContextDerived() override {
        }
};
int main() {
  // test 1: derived class
  LoggerContextDerived c1 {LoggerArea::LOGGER_DOWNLOAD};   // ok
  cout << "General : " << c1.getGeneral() << endl;         // ok
  cout << "Context : " << c1.getContext() << endl;         // ok
  LoggerContext * c2 = &c1;                                // ok
  // test 2: derived none class
  LoggerContextNone * c3 = LoggerContextNone::getInstance();   // ok
  LoggerContext * c4 = c3;                                     // g++ error:
    // error: ‘LoggerContext’ is an inaccessible base of ‘LoggerContextNone’
  LoggerContext * c5 = LoggerContextNone::getInstance();       // g++ error:
    // error: ‘LoggerContext’ is an inaccessible base of ‘LoggerContextNone’
  return 0;
}