如何使用__LINE__或其他方法从调用点获取行号?

5

考虑我写的这个简短的程序:

    #include <iostream>
    
    template<bool Debug = false>
    constexpr int add(const int& a, const int& b) { 
        if (Debug)
            std::cout << __FUNCTION__ << " called on line " << __LINE__ << '\n';
        return (a + b);
    }
    
    int main() {
        std::cout << add(3, 7) << '\n';
        std::cout << add<true>(5, 9) << '\n';
        return 0;
    }

这很好用,而且它给出了正确的输出:

10
add called on line 6
14

然而,我希望打印的行号是程序调用点的行号,在这个程序中应该是第12行。那么我如何使用 "__LINE__" 或其他方法来给我从函数被调用的地方获取行号呢?期望输出结果为:
10
add called on line 12
14

如果可能的话,我希望它是从功能本身生成的。

-编辑-

作为对读者的说明,我对所有选项都持开放态度,但由于我的当前构建环境和使用Visual Studio,我仅限于C++17。

2个回答

6
您可以这样调用它:
template<bool Debug = false>
constexpr int add(const int& a, const int& b, int loc = __LINE__) { 
    if (Debug)
        std::cout << __FUNCTION__ << " called on line " << loc << '\n';
    return (a + b);
}

int main() {
    std::cout << add(3, 7) << '\n';
    std::cout << add<true>(5, 9, __LINE__) << '\n';
    return 0;
}

输出:

10
add called on line 14
14


此外,您可以定义一个宏来跳过第三个参数:
#define add_Debug(a, b) add<true>(a,b,__LINE__)

C++20及以后版本

在C++20中,我们获得了std::source_location,其中包含有关行号、函数和文件名的信息。如果您的编译器支持它,则可以使用它。例如(已在g++ 9.3.0上测试)。您将不再需要宏:

#include <iostream>
#include <experimental/source_location>

using source_location = std::experimental::source_location;

template<bool Debug = false>
constexpr int add(const int& a, const int& b, source_location l = source_location::current()) { 
    if (Debug)
          // this will print 'main' as function name
          //std::cout << l.function_name() << " called on line " << l.line() << //'\n';
        std::cout << source_location::current().function_name() << " called on line " << l.line() << '\n';


    return (a + b);
}

int main()
{
    std::cout << add(3, 7) << '\n';
    std::cout << add<true>(5, 9) << '\n';

    return 0;
}

输出:

10
add<true> called on line 16
14

使用 source_location,你不再需要使用宏了。它会正确打印行号。


这应该可以运行...但是,每个函数都要添加这个...嗯 - Francis Cugler
1
我听说C++20有一个新的建议功能...迫不及待地想开始使用它! - Francis Cugler
1
它确实可以工作,但是除非你显式地传递 __LINE__,否则你将得到不正确的行信息,例如 add<true, __LINE__>(5, 9)。使用 source_location,这不是问题。你只需将其用作函数参数,它将打印调用该函数的行的信息。 - Waqar
1
好的,我已经更改了编译器探索器的设置以匹配我的环境:x64 MSVC 19.24,并将其更改为c++17。它在那里编译得很好。但是,在Visual Studio 2017中,当我以调试模式编译时,它会生成“C2672”和“C2975”编译错误。然而,当我切换到发布模式时,它可以编译、构建、运行并产生正确的输出...有点奇怪,我想知道这是否是Visual Studio的一个Bug... - Francis Cugler
1
可能是MSVC中的一个bug。因为那个模板函数中没有C++17的特性。 - Waqar
显示剩余8条评论

2
如果您能够在Linux上使用一些最新的GCC(在2020年7月,这意味着GCC 10),那么您可以使用g++ -g -O -Wall编译,然后使用Ian Taylor的libbacktrace(就像我们在RefPerSys中所做的那样),因为该库解析DWARF调试信息。
您可以将该库移植到您的操作系统和调试信息格式中,或者找到某些等效的库。
今天,可能更粗糙的方法是使用宏包装一些函数。例如,不是使用void foo(void);调用foo(),而是:
extern void foo_at(const char*fileno, int lineno);
#define foo() foo_at(__FILE__,__LINE__);

实际上,C++20 应该添加 特性测试source_location,但你可能需要等待几个月才能使用支持它的编译器。考虑从源代码编译最近版本的 GCC。据我所知,MinGW 64 可以在 Windows 上运行(所以要先获得使用许可)。

是的,但那有点向C编程倒退了,我正在努力推动C++的极限并向前发展! - Francis Cugler

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