【问题标题】:Limit code duplication when decorating a function装饰函数时限制代码重复
【发布时间】:2025-12-30 02:25:06
【问题描述】:

我是 C++ 编程的新手,想问一下如何以最有效的方式实现某些东西。假设我有一个 A 类,它有两个函数 foo 和 bar。 Main 将实例化 A 的对象并调用 foo。这个类做了一些计算成本很高的事情,其中​​ foo 可能会调用 bar ,反之亦然,回溯等等,直到计算出一些结果。我现在想为这个类实现一个装饰器之类的东西,其中功能栏现在与以前一样,但在返回之前,例如打印一些中间结果,同时保持原始实现可用于不需要这些中间结果的情况。其他一切都保持不变。

我想知道我会以最好的方式实现这样的东西。我当然可以在 A 类的 bar 末尾添加一些 if 语句并检查 main 中设置的一些成员变量,但是由于这些函数将被大量调用,这将添加许多实际上不需要的额外检查,如一次检查就足够了。我还可以创建 A 类的副本,我们称它为 B,并在那里修改 bar。但这会复制大部分代码,并且需要对 A 和 B 进行后续更改。我还可以在 A 中创建函数 bar 并让 B 从 A 派生并覆盖它,但是我只需进行这些检查以调度决定的形式,不是吗?

【问题讨论】:

  • 我会使用可以配置为开启或关闭的日志库。
  • 检查布尔值比通过函数调用间接(这就是你的装饰)要便宜得多。对于布尔检查,机器级别的实现将是对特定内存位置的条件跳转;现代 CPU 的分支预测器早餐吃这个。 – 对于装饰器,必须加载装饰器函数位置,然后跳转或调用该位置,这将加载另一个位置(您的原始函数)并调用它;由于这些功能可能相距甚远,因此分支检测器的工作量要少得多。
  • @John Kugelman:如果仍然必须在运行时决定是否应该打印中间结果,但在 main 中只做一次,这将如何工作?就像,它仍然是一个二进制文件,是否应该进行打印将基于例如程序参数中的标志
  • @datenwolf:我还认为,如果在对象的剩余生命周期中调用第一个 foo 后,条件基本上是固定的,那么跳转预测可能会处理它。我只是想知道是否有一种惯用的方式来做这样的事情
  • 除非出现明显的效率问题,否则不要担心效率。您正在尝试执行的操作,可选择打印结果以帮助您了解代码中发生的情况——这正是记录器的用途。使用记录器,然后您可以在要调试此代码时打开日志,并在不想调试时将其关闭。不需要重新发明*,是吗? :)

标签: c++ code-duplication


【解决方案1】:

我建议使用 bool 参数

class A{
    public:
    void foo(/*params*/,bool should_print=false){
        if (should_print){
            //print
        }
    }
    void bar(/*params*/,bool should_print=false){
        if (should_print){
            //print
        }
    }
};

如果要打印,请将参数 should_print 设置为 true,否则不要传递。

您也可以使用模板作为标志。所以(INside A 类)

template<bool should_print>
void bar(/*params*/){
    if constexpr (should_print){
        //print
    }
}

这将在编译时评估 if ,因此它会更快,但您必须在石南花中为不在它的 .cpp 中的类定义模板函数@

注意if constexpr 仅适用于c++17,如果您使用较旧的标准,您可以使用常规的if。任何称职的编译器也会以这种方式评估 if 编译时

【讨论】:

  • 默认参数不会产生多个函数。它们只是语法糖,如果省略参数,则会在调用站点传递默认值。
  • should_print 如何从foo 传递到bar,反之亦然?它需要添加到每个调用的所有调用中。这适用于两个版本。你能更新你的答案来解释吗?
  • @JohnKugelman 我认为它是班上的一员,因为当他说他不想那样做时,OP 也提出了同样的建议,因为这导致了很多额外的检查。
  • 班级成员会很有意义。不过,您的回答表明它是一个论点,是吗?
  • @JohnKugelman 使用默认参数是对的(我检查了只有模板生成更多功能)。在我的第二个示例中,bar() 在类内(也是 foo),但我不想欺骗我编写的代码。