【问题标题】:c++: logger class without globals or singletons or passing it to every methodc++:没有全局变量或单例或将其传递给每个方法的记录器类
【发布时间】:2011-08-18 04:18:01
【问题描述】:

有谁知道是否有可能有一个像记录器这样的类而没有:

  • 使用单例或全局(例如 std::cout)

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

我以记录器类为例,但我的应用程序中有一些类可以从中受益(例如,撤消管理器)。

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

  • 使用单例对测试来说是有问题的(以及许多原因,使用单例通常不是一个好主意)。全局也是一样。此外,没有什么能保证应用程序中只有一个实例,甚至不是必需的(例如,为什么没有 2 个记录器?)

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

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

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

【问题讨论】:

  • 我见过一个日志类,它只会在每次将语句写入日志时打开日志文件,并且日志名称是硬编码的(如果你想写入不同的文件,可以选择传入) )。没有全局变量、单例或将对象传递给每个方法,但不完全是您正在寻找的黑魔法解决方案。事实上,我仍在努力从产品中删除该日志类:)
  • 如果记录器类存在,那么您可以去编辑每个方法吗?如果是这样,那么我可以提出一些建议。
  • @iammilind:你的意思是记录器类的每个方法吗?是的,有可能,我迫不及待地想听听你的建议 :) @DXM : ))))
  • 对不起,我可能误解了你的问题。我以为您想记录在整个代码流中被调用的所有函数。看到下面的答案后,您似乎想要一些不同的东西。或者可能是我还没有很好地理解,你的问题到底是什么。 :)
  • 看看Hypodermic。看起来不是很漂亮,但我认为这可能是一个解决方案。

标签: c++ logging dependency-injection singleton


【解决方案1】:

我想你可以用 Log4j 包做类似于 Java 中所做的事情(并且可能用它的 Log4c 版本完成):

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

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

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

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

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

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

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

【讨论】:

  • 听起来很明智,但是,如果您使用工厂,您仍然必须将工厂传递给您的类构造函数,不是吗?
  • 不,你没有。如果工厂的构建方法使得factory 可以是functor,您可以在调用getLogger() 的代码中即时创建工厂对象的实例。
  • 我真的看不出这比单例有什么好处。如果他们都必须包装一些错误日志文件句柄,那么该文件句柄已经是一个单例。用一个单一的成员字段结构或简单的单例类包装它,只有文件指针成员,没有虚拟方法,无论如何都等同于单例文件句柄对象。我也不喜欢单例,但这听起来并不有利,作为一种绕过它们的方式。它本质上是 getSingletonSuitableFor(someParam)。
  • user1024732:在多线程编程中,写入日志文件需要同步。在某些项目中允许创建多个日志文件(每个操作系统线程一个文件),而在其他项目中则不允许。在多个并发事务处理中,每条日志消息都必须以事务 ID 为前缀,因此必须有一些中间的日志帮助对象被告知它正在处理的当前事务 ID,并相应地修改日志消息字符串。一般来说,软件越大,它对新手的关注就越少。
【解决方案2】:

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

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,那么很容易存根进行测试。

【讨论】:

  • 太好了,它与记录器配合得很好,但是需要状态的类呢?例如,撤消管理器会保留用户操作的历史记录,因此需要跟踪正在发生的事情。
  • @Dinaiz:没问题。将record 设为模板,为您的状态类定义operator&lt;&lt;,就完成了。
【解决方案3】:

我不认为 C++ 中有一个好的替代方案,但是 Emacs Lisp 中有一个可能值得考虑的替代方案:dynamic binding of variables。基本概念是这样的,当你引用一个变量时,你访问的不一定是全局变量,而是最后一个在执行路径中定义的同名变量。在伪 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,因为 MyLogger 将覆盖全局变量只要它在范围内,甚至在foo() 内。当 MyLogger 超出范围时,第三次调用将返回给 GlobalLogger。这允许为选定的代码片段使用自定义记录器覆盖全局记录器,而无需通过所有代码传递记录器对象,也无需设置全局变量。

真正的 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() 操作可以隐藏在作用域保护之后,并且需要采取多线程问题照顾。但与简单的单例或全局变量相比,基本概念可能会起作用并提供更大的灵活性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-10
    • 2012-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多