【问题标题】:Header-only class + Undefined reference to function ONLY IF returning object of that class仅标头类 + 仅当返回该类的对象时对函数的未定义引用
【发布时间】:2018-01-03 07:55:15
【问题描述】:

我对 GCC 5.4.0 链接器有一个非常奇怪的问题。我有这些文件:

spline.h, utils.h/cpp, main.cpp

spline.h 是一个仅用于将点拟合到样条的实用程序类。

1) 我用 utils.cpp 和 CMake 创建了一个库:

add_library(utils_lib utils.cpp)

utils.h#includeing spline.h

2) 我从main.cpp 创建我的二进制文件:

add_executable(hello_world main.cpp)
target_link_libraries(hello_world utils_lib)

3)在utils.cpp里面,我有这个功能:

tk::spline fitSpline(const std::vector<double>& x,
                     const std::vector<double>& y)
{
    tk::spline output;
    output.set_points(x,y);
    return output;
}

所以,如果我尝试在main.cpp 中使用这个函数:

auto my_spline = fitSpline(x,y);

然后我得到这个链接器错误:

undefined reference to `fitSpline(std::vector<double, std::allocator<double> > const&, std::vector<double, std::allocator<double> > const&)'

但是,如果我将fitSpline 的返回值更改为double,例如:

double fitSpline(const std::vector<double>& x,
                 const std::vector<double>& y)
{
    tk::spline output;
    output.set_points(x,y);

    return 0.0;
}

然后我不再收到链接器错误!它编译得很好。我真的不明白问题是什么,有什么提示吗?

谢谢!

【问题讨论】:

  • utils.h 中是否存在fitSpline 的函数签名?目前唯一能想到的。可以的话,把相关代码贴在utils.*main.cpp
  • @hnefatl 是的,签名在那里。正如我所说,如果我只是更改返回值类型,一切都会正常工作。我刚刚意识到spline.h 的所有内容都在匿名命名空间中,这肯定是原因!那么我需要在每个 cpp 文件中 #include 吗?
  • 如果他们真的在anonymous namespace 中,那么文件之外的任何东西都应该无法访问它们。通过签名,我的意思是问您是否有该功能的拆分定义和实现,并且忘记更新其中之一。在main.cpp 中包含spline.h 可能会有所帮助,但我真的不知道为什么会这样。发布更多代码会有所帮助。
  • 是的,它们在utils.cpputils.h 中具有相同的签名。我刚刚在main.cpputils.cpputils.h 中尝试了#includeing spline.h,但它仍然不起作用。仅当我跳过utils 并在main.cpp 中直接使用spline.h 而不是将其包装在函数中时才有效。
  • 其实spline.h 开头是: // 未命名的命名空间只是因为实现在这个 // 头文件中,我们不想将符号导出到 obj 文件 namespace { namespace tk { (Sorry对于格式化,我可以在 cmets 部分做正确的代码吗?)

标签: c++ gcc cmake linker undefined-reference


【解决方案1】:

有两个翻译单元。 utils_lib 的 TU 和 main.cpp 的 TU。

未命名命名空间中的名称都在唯一的命名空间中并且有效地具有内部链接(从 C++11 开始,它们被定义为具有内部链接)。

因此,由于有效的内部链接,尝试从不同的 TU 引用像 tk::spline 这样的类型将失败。

如果对象是无状态且不共享的,则重复使用 spline.h 通常没问题。但在这里你没有这样做。通过在一个 TU 中创建对象并尝试通过不同 TU 中的类型存储它们,您将它们视为可替代的。这将失败,因为它们具有不同的唯一命名空间名称。

如果您真的不想在仅包含标头的库中导出不必要的符号,则应仅对这些符号使用内部“详细”命名空间,而将用户应该知道的符号留在外部命名空间中。

顺便说一句,这些都与cmake 或任何构建系统无关。但是,由于您的问题使用cmake 使结构更清晰(顺便说一句,做得很好),因此标签可能没问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-01
    • 1970-01-01
    • 2010-11-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多