共享库:具有部分模板专业化和显式模板实例化的未定义参考

时间:2017-02-20 10:33:27

标签: c++ shared-libraries undefined-reference partial-specialization template-instantiation

说,有一个第三方库在头文件中包含以下内容:

foo.h

namespace tpl {
template <class T, class Enable = void>
struct foo {
  static void bar(T const&) {
    // Default implementation...
  };
};
}

在我自己的库的界面中,我应该为我自己的类型提供此foo的部分特化。所以,让我们说我有:

xxx.h

# include <foo.h>

namespace ml {
struct ML_GLOBAL xxx {
  // Whatever...
};
}

namespace tpl {
template <>
struct ML_GLOBAL foo<::ml::xxx> {
  static void bar(::ml::xxx const&);
};
}

其中ML_GLOBAL是特定于编译器的可见性属性,以确保符号可用于动态链接(默认情况下,我的构建系统隐藏生成的共享中的所有符号库)。

现在,我不想透露我对bar的实施情况,因此我使用显式模板实例化

xxx.cpp

# include "xxx.h"

namespace tpl {
void foo<::ml::xxx>::bar(::ml::xxx const&) {
  // My implementation...
}

extern template struct foo<::ml::xxx>;
}

在某些消费者应用程序(我的共享库也链接在一起)实际使用此tpl::foo<::ml::xxx>::bar函数的时候,我得到了{{1>未定义的引用错误{{1符号。实际上,在生成的共享库上运行tpl::foo<::ml::xxx, void>::bar不会显示nm -CD符号的痕迹。

我到目前为止所尝试的是关于放置tpl::foo<::ml::xxx, void>的位置的不同组合(例如关于显式模板实例化本身,关于GCC明显抱怨与Clang不同的内容)以及是否有第二个模板参数{ {1}}。

问题在于这是否与原始定义没有通过来自第三方库附加的可见性属性(ML_GLOBAL)这一事实相关,或者我是否真的错过了这里的内容?如果我没有错过任何东西,那么我真的被迫在这种情况下暴露我的实现吗? [... * 咳嗽 *看起来更像是编译器缺陷,说实话* 咳嗽 * ...]

1 个答案:

答案 0 :(得分:3)

事实证明这是一种误报。然而,这次捕获花了我几个小时才终于记住为什么消费者看不到这个符号。这真的很微不足道,但我想在这里发布它,以便将来的访问者碰巧拥有相同的设置。基本上,如果您使用链接描述 [1]或(纯)版本脚本 [2](使用{{指定) 1}}链接器选项),然后不要忘记为那些基于第三方的--version-script第三方符号(或根据您的情况设置)设置global可见性。在我的情况下,我最初有以下内容:

tpl::foo*

我显然必须改为

{
global:
  extern "C++" {
    ml::*;
    typeinfo*for?ml::*;
    vtable*for?ml::*;
  };

local:
  extern "C++" {
    *;
  };
};

为了正确地链接所有内容并获得预期的结果。

希望这会有所帮助。

奖金

一个好奇的读者可以问,&#34;为什么你在组合显式可见性属性和链接器/版本脚本来控制符号的可见性,当已经存在{ global: extern "C++" { tpl::foo*; ml::*; typeinfo*for?ml::*; vtable*for?ml::*; }; local: extern "C++" { *; }; }; -fvisibility=hidden时应该做的那些选择?&#34;。

答案是他们当然这样做,我确实使用它们来构建我的共享库。但是,有一个问题。通常的做法是将您的共享库静态使用的某些内部库(私有)链接到该库中(主要是为了完全隐藏这些依赖项)(但请记住,共享库附带的头文件也应该是设计合理,以实现这一目标)。好处很明显:ABI干净且可控,减少了共享库消费者的编译时间。

以Boost为例,作为此类用例的最广泛候选者。将Boost中所有经过严格模板化的代码私有封装到您的共享库中并从ABI中删除任何Boost符号将大大减少共享库消费者的界面污染和编译时间,而不包括您的软件组件看起来也是专业开发的事实。

无论如何,事实证明,除非您想要链接到共享库的静态库本身也使用-fvisibility-inlines-hidden-fvisibility=hidden选项构建(是一个荒谬的期望,因为没有人会默认分配带有隐藏接口符号的静态库,因为它会破坏它们的目的),它们的符号将不可避免地仍然可见(例如,通过-fvisibility-inlines-hidden),无论事实上,您正在使用这些选项构建共享库本身。也就是说,在这种情况下,您有两种方法可以解决它:

  1. 使用nm -CD <shared-library>-fvisibility=hidden选项手动重建这些静态库(您的共享库依赖项),鉴于其潜在的第三方来源,显然并非总是可行/实用。
  2. 使用链接器/版本脚本(如上所述)在链接时提供,以指示链接器强制导出/隐藏共享库中的正确符号。