不使用宏将调用者的__FILE__ __LINE__传递给函数

28

我习惯于这样:

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__)更糟糕。

所以,有什么建议吗?


14
不要使用名称“_Commit”。以下划线开头且第二个字符为大写字母的名称被保留供实现使用,您的使用意味着您的程序具有未定义的行为。 - R.. GitHub STOP HELPING ICE
4
换句话说,编译器允许定义一个名为“_Commit”的宏,这可能会破坏你的代码。 - jalf
是否允许不可移植并手动或通过脚本使用addr2line查找函数地址?在这种情况下,您可以使用GCC的__builtin_return_address。记录地址而不是名称并不美观,但是假设您只需要知道何时出了问题(这意味着很少),那么可能会有所帮助。好处是它“只是有效”。我在异常中传递了最后三个调用者,这也可以正常工作(除了难以解密)。 - Damon
猜猜看,这是做不到的。__FILE__和__LINE__都是宏,必须在宏处理上下文中调用才能产生所需的效果。 - user207421
3个回答

53

所以,您想使用文件和行信息记录日志(或其他内容),而且您不想使用宏,对吗?

说到底,这在C ++中根本无法完成。无论您选择什么机制--无论是内联函数、模板、默认参数还是其他什么--如果您不使用宏,则最终会得到记录函数的文件名和行号,而不是调用点。

使用宏。这是一个地方,宏真的是无可替代的。

编辑:

甚至C++ FAQ也表示,有时宏是两害相权取其轻的做法之一。

编辑2:

如下方评论中Nathon所说,在使用宏的情况下,最好是明确地指出。给您的宏命名为宏式名称,例如COMMIT()而不是Commit()。这将向维护者和调试器表明正在进行宏调用,并且在大多数情况下应该有助于避免冲突。这两件事都很好。


4
+1 -- 我无法相信我正在给一个建议“使用宏”的回答点赞,但无论如何还是点赞。 - Billy ONeal
11
你可能需要使用宏,但这并不意味着你不能明确地表达它。 COMMIT()显然是一个宏调用(按照通常的约定),因此不应与你400kloc应用程序框架中的任何内容发生冲突。 - nmichaels
8
让用户知道这是宏而不是函数,可以提高可读性。否则,他们可能会尝试在不合适的地方使用宏(例如尝试创建指向宏的函数指针)。 - Billy ONeal
2
在宏的情况下,你需要大喊“这是一个宏”。宏本质上是危险的,应该标记为宏。 - KeithB
1
很有趣,为什么C++中没有这样的功能呢?它将像默认参数一样工作。比如有: void commit (int a, int b=2)我们可以有 void commit (int a, int b=2, string file=_CALLER::FILE, int line=_CALLER::LINE)在每次调用中,编译器都可以用文件和行替换特殊的_CALLER成员,调用的位置取决于源代码位置 - 编译器在解析CPP文件时知道的事情,但值不是常量。为什么不存在这样的东西呢? - Nuclear
显示剩余4条评论

6

在 C++20 之前,一些编译器有内置函数可以以相同的方式使用,例如 GCC 中的 __builtin_FILE() - HolyBlackCat

0
你可以使用默认参数和预处理技巧的组合来将调用者文件传递给函数。具体如下:
  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 定义一个宏,在编译此文件之前。

$< 是 Make 的替换符号,用于规则的先决条件的名称,本例中是源文件的名称。因此,每个翻译单元都将有自己的 db_caller_file 变量,其值为包含文件名的字符串。

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


我们编译翻译单元,而不是文件。恐怕这样做不会起作用。 - einpoklum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接