【问题标题】:Passing the caller __FILE__ __LINE__ to a function without using macro将调用者 __FILE__ __LINE__ 传递给函数而不使用宏
【发布时间】:2011-05-10 22:23:26
【问题描述】:

我已经习惯了:

class Db {
  _Commit(char *file, int line) {
    Log("Commit called from %s:%d", file, line);
  }
};

#define Commit() _Commit(__FILE__, __LINE__)

但最大的问题是我在全球范围内重新定义了Commit 这个词,而在一个 400k 行的应用程序框架中这是一个问题。而且我不想使用像 DbCommit 这样的特定词:我不喜欢像 db->DbCommit() 这样的冗余,或者在任何地方手动传递值:db->Commit(__FILE__, __LINE__) 是最糟糕的。

那么,有什么建议吗?

【问题讨论】:

  • 不要使用名称_Commit。以下划线开头并以大写字母作为第二个字符,它是为实现而保留的,您使用它意味着您的程序具有未定义的行为。
  • 换句话说,允许编译器定义一个名为_Commit的宏,这会破坏你的代码。
  • 是否允许不可移植且必须手动或通过使用addr2line 的脚本查找函数地址?在这种情况下,您可以使用 GCC 的 __builtin_return_address。记录地址而不是名称并不漂亮,但大概您只需要知道什么时候出了问题(这意味着很少),这样就可以了。好消息是它“正常工作”。我在异常中传递了最后三个调用者,这也很好(除了难以破译)。
  • 猜猜看。这是不可能的。 FILELINE 都是 宏,并且必须从宏处理上下文中调用才能获得所需的效果..

标签: c++ c-preprocessor


【解决方案1】:

所以,您希望使用文件和行信息进行日志记录(或其他操作),并且您不想使用宏,对吗?

归根结底,它根本无法用 C++ 完成。无论您选择什么机制——内联函数、模板、默认参数或其他——如果你不使用宏,你最终会得到日志函数的文件名和行号,而不是呼叫点。

使用宏。这是它们真正不可替代的地方。

编辑:

即使C++ FAQ 也表示宏有时是两害相权取其轻。

编辑2:

正如 Nathon 在下面的 cmets 中所说,如果您确实使用宏,最好明确说明。给你的宏命名,比如COMMIT() 而不是Commit()。这将使维护人员和调试人员清楚地知道正在进行宏调用,并且在大多数情况下它应该有助于避免冲突。都是好东西。

【讨论】:

  • +1 -- 我不敢相信我会支持“使用宏”的答案,但无论如何都是 +1。
  • 您可能必须使用宏,但这并不意味着您不能明确说明。 COMMIT() 显然是一个宏调用(按照正常约定),因此不应与 400kloc 应用程序框架中的任何内容冲突。
  • @Nocola:通过让用户知道它是宏而不是函数,可读性得到了增强。否则他们可能会尝试在没有意义的地方使用宏(例如尝试创建指向它的函数指针)(
  • 正如@Billy 所说,可读性与能够理解您正在查看的代码有关,并且使您的宏看起来像宏是其中的重要部分.
  • @Nicola 在宏的情况下,您想要大喊“这是一个宏”。宏本质上是危险的,应该这样标记。
【解决方案2】:

等到 C++20,你可以使用 source_location

https://en.cppreference.com/w/cpp/utility/source_location

【讨论】:

  • Pre-C++20 一些编译器具有可以以相同方式使用的内置函数,例如__builtin_FILE() 在 GCC 中。
【解决方案3】:

您可以结合使用默认参数和预处理器技巧将调用程序文件传递给函数。如下:

  1. 函数声明:

    static const char *db_caller_file = CALLER_FILE;
    
    class Db {
        _Commit(const char *file = db_caller_file) {
        Log("Commit called from %s", file);
      }
    };
    
  2. 在类头文件中声明db_caller_file 变量。 每个翻译单元都有一个const char *db_caller_file。它是静态的,因此不会干扰翻译单元。 (没有多重声明)。

  3. 现在CALLER_FILE 是一个宏,将由gcc 的命令行参数生成。实际上,如果使用自动化的 Make 系统,其中有源文件的通用规则,它会容易得多:您可以添加一个规则来定义宏,以文件名作为值。例如:

    CFLAGS= -MMD -MF $(DEPS_DIR)/$<.d  -Wall -D'CALLER_FILE="$<"'
    

-D 在编译这个文件之前定义了一个宏。 $&lt; 是 Make 替换规则的先决条件的名称,在本例中是源文件的名称。因此,每个翻译单元都有自己的 db_caller_file 变量,其值为字符串,包含文件名。

同样的想法不能应用于调用者行,因为同一个翻译单元中的每个调用应该有不同的行号。

【讨论】:

  • ... 但我们编译的是翻译单元,而不是文件。恐怕这行不通。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-11-26
  • 2013-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-16
相关资源
最近更新 更多