【问题标题】:How can I prevent a logging string being compiled based on a compile-time verbosity level?如何防止基于编译时详细级别编译日志记录字符串?
【发布时间】:2021-05-18 06:52:03
【问题描述】:

如果在“logger.h”文件中我有这个:

template <int CompileTimeVerbosity>
struct Logger
{
    static inline int compileTimeVerbosity = CompileTimeVerbosity;
    void log(const std::string& msg) { }
};

#define LOG(logger, msgVerbosity, msg) \
if constexpr (msgVerbosity >= logger.compileTimeVerbosity) \
{ \
logger.log(msg); \
} \

inline Logger<4> loggerObj;

我将此标头包含在一堆 .cpp 文件中。然后在其中一个中我做:

void func()
{
    LOG(loggerObj, 2, std::string("Sorry, you called a ") + std::to_string(int()) + " on a " + std::to_string(int()))
}

因为内联 Logger loggerObj 被指示只定义一次,并且我们在多个 .cpp 文件中包含头文件,所以我们不知道它是在哪里定义的。我们只知道编译器在“一些”.cpp 中定义了一次。因此,当我们在某些 .cpp 中调用 func() 时,“loggerObj”标识符可能类似于“extern Logger loggerObj;”。如果是这种情况,我是否正确地说编译器当时没有完整的定义?这也意味着 func() 函数中的所有内容都将被编译,然后只有链接器可以针对可能在另一个翻译单元中定义的对象执行 if constexpr 语句。

我不确定这是否是链接时优化,但是 LOG() 宏调用中的内容应该很容易被链接器优化,对吧?换句话说,生成的代码不应包含 std::string() + std::string() 等?

虚幻引擎有与此类似的日志系统,但我认为它可能完全在宏级别上工作,所以在宏调用 UELog(category, verbosity, msg) 我认为整行只是被预处理器省略而不是复制一些东西比如 if constexpr。

有什么办法可以做到这一点吗?我找不到这部分日志系统的源代码。

【问题讨论】:

  • “链接器可以执行 if constexpr 语句” - 不,if constexpr 将在编译步骤中评估
  • if constexpr不是的正确工具。它一点也不像#if
  • @user7860670 真的吗?那是因为它知道来自 extern Logger logger1 的模板类型; ?或者它在任何情况下都能做到吗?因为我认为编译器会编译单个翻译单元中的内容,而链接器会完成其余的工作,因此当找不到标记为 extern 的变量时,为什么会出现链接器错误,而不是编译器错误。
  • @StoryTeller-UnslanderMonica 如果'verbosityLevel'较低,他们都会删除那段代码,只是一个在预处理器步骤从源代码中删除它,另一个从编译代码中删除它在编译器步骤,对吧?
  • 没有。这是一个常见的误解。 stackoverflow.com/q/46512248/817643

标签: c++ preprocessor


【解决方案1】:

您的解决方案几乎是正确的。它不会编译,因为compileTimeVerbosity 不是 constexpr,但如果你让它 constexpr 替换

static inline int compileTimeVerbosity = CompileTimeVerbosity;

static constexpr int compileTimeVerbosity = CompileTimeVerbosity;

然后它会做你想做的事:https://godbolt.org/z/azecWMY86

附带说明,切勿创建扩展为 if (...) { } 的宏。看看这个例子:

if (...)
  LOG(...)
else
  ...

您可能认为else 分支指的是外部if,但实际上宏内部的if 会劫持它。

解决此问题的方法是始终将宏代码包装在 do { ... } while (false) 中,例如:

#define LOG(logger, msgVerbosity, msg) \
  do { if constexpr (...) { ... } } while(false)

这个循环只执行一次,它没有讨厌的陷阱。

【讨论】:

  • 附注:更好的规则是始终在if 之后使用{ },即使只有一个语句。该规则避免了一系列潜在问题,包括上述将宏扩展为 if (...) { } 的情况。
  • 我完全赞成在if 之后总是使用{ }。我宁愿不同意这条规则“更好”,但这是无关紧要的。两者都用。当谈到用 C++ 宏保护自己免于击中脚时,两个级别的预防措施对我来说似乎不是太多的预防措施:)
  • @AndreiMatveiakin 我可以在它周围添加额外的括号,而不是在宏中添加额外的括号,不是吗,模拟有人在第一个外部 if 语句中添加额外的括号?
  • @Zebrafish 这样会更好,但仍然不完美。如果用户认为LOG 是一种类似函数,他们会习惯性地在其后添加分号:if (...) LOG(...); else ...。但是if (...) { ... }; else ... 不会编译。如果您希望 LOG 在所有可能的上下文中在语法上表现得像一个函数,那么 do { ... } while (false) 是实现该 AFAIK 的最合理方法。
猜你喜欢
  • 1970-01-01
  • 2011-03-06
  • 1970-01-01
  • 1970-01-01
  • 2020-08-18
  • 2011-04-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多