c++:logger类,不带全局变量或singleton,或将其传递给每个方法

c++: logger class without globals or singletons or passing it to every method

本文关键字:方法 singleton c++ 全局变量 logger      更新时间:2023-10-16

有人知道是否可以在没有的情况下拥有像记录器这样的类吗

  • 使用单例或全局(a la std::cout(

  • 将实例/指针/引用传递给每个需要它的方法

我举了一个logger类的例子,但我的应用程序中有几个类会从中受益(例如,undo管理器(。

每个解决方案都有几个问题:

  • 使用singleton对测试来说是有问题的(还有很多原因,使用singleton通常不是一个好主意(。全球化也是如此。此外,没有什么能保证应用程序中只有一个实例,而且这甚至不是一个要求(例如,为什么不有两个记录器?(

  • 将它传递给每个对象构造函数(依赖项注入(,会导致大量的样板代码,并且很容易出错,因为您必须多次复制/粘贴相同的代码。是否可以认真考虑在每个类的构造函数中都有一个指向Logger的指针???????

所以我想知道是否还有第三种选择,在C++中,我从未听说过?对我来说,这听起来需要一些黑魔法,但我对我在堆栈溢出中学到的一些技术感到惊喜,这些技术在谷歌上找不到,所以我知道这里有一些真正的大师;(

令人惊讶的是,我发现了很多关于如何设计singleton,或者为什么不应该使用singleton的讨论,但我找不到一篇解决我问题的帖子。。。

我想你可以用Log4j包做一些类似于Java中做的事情(可能用它的Log4c版本做(:

有一个可以返回多个记录器实例的静态方法:

Logger someLogger = Logger.getLogger("logger.name");

getLogger()方法没有返回singleton对象。它正在返回命名的记录器(如果需要,则创建它(。Logger只是一个接口(在C++中,它可能是一个完全抽象的类(——调用者不需要知道正在创建的Logger对象的实际类型。

您可以继续模拟Log4j,并有一个getLogger()的重载,它也接受一个工厂对象:

Logger someLogger = Logger.getLogger("logger.name", factory);

该调用将使用factory来构建记录器实例,使您能够更好地控制正在创建的底层Logger对象,这可能有助于您进行嘲讽。

因此,不需要将任何东西传递到您自己代码的构造函数、方法等中。您只需在需要时获取所需的命名Logger并登录到它。根据您编写的日志代码的线程安全性,您可以将getLogger()返回的内容作为类的静态成员,因此每个类只需调用getLogger()一次。

一个带有一些静态方法的类怎么样?

class Logger
{
public:
  static void record(string message)
  {
    static ofstream fout("log");
    fout << message << endl;;
  }
  ...
};
...
void someOtherFunctionSomewhere()
{
  Logger::record("some string");
  ...
}

没有Singleton,没有全局变量,但任何可以看到Logger.h的代码都可以调用成员函数,如果所有公共成员函数都返回void,那么就很容易进行测试。

我认为C++中没有一个好的替代方案,但Emacs Lisp中有一个可能值得考虑:变量的动态绑定。基本概念是这样的,当你引用一个变量时,你不一定会通过名称访问全局变量,而是通过相同的名称访问执行路径中最后定义的变量。在伪C++代码中,这看起来像这样:

// Pseudo-C++ with dynamic binding
Logger logger = Logger("GlobalLogger");
void foo() { logger.log("message"); }
int main() 
{
   foo(); // first
   {
      Logger logger = Logger("MyLogger");
      foo(); // second
   }
   foo(); // third
}

在这个伪C++示例中,当您第一次调用foo()时,将使用GlobalLogger,而当您第二次调用它时,将调用MyLogger,因为只要全局变量在作用域内,即使在foo()内,MyLogger也会覆盖全局变量。第三个调用将返回到GlobalLogger,因为MyLogger退出了作用域。这允许为选定的代码段使用自定义记录器覆盖全局记录器,而无需在所有代码中传递记录器对象,也无需设置全局变量。

真正的C++没有动态绑定,但应该可以复制它的各个方面:

std::stack<Logger> logger = { Logger("GlobalLogger") };
void foo() { logger.top().log("message"); }
int main()
{
    foo();
    {
      logger.push(Logger("MyLogger"));
      foo();
      logger.pop();
    }
    foo();
}

为了进一步清理,堆栈应该隐藏在DynamicBinding类中,手动.push((/.pop((操作可以隐藏在作用域保护后面,并且需要处理多线程问题。但是,与简单的单例变量或全局变量相比,基本概念可能会起作用,并提供更大的灵活性。