提问人:Alexander Shukaev 提问时间:2/20/2017 最后编辑:Alexander Shukaev 更新时间:2/27/2017 访问量:721
共享库:具有部分模板专用化和显式模板实例化的未定义引用
Shared Library: Undefined Reference with Partial Template Specialization and Explicit 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
tpl::foo<::ml::xxx, void>::bar
nm -CD
tpl::foo<::ml::xxx, void>
到目前为止,我尝试过的是放置位置的不同组合(例如,在显式模板实例化本身上,关于GCC与Clang不同明显抱怨的内容)以及有/没有第二个模板参数。ML_GLOBAL
void
问题是,这是否与原始定义由于来自第三方库而没有附加可见性属性()有关,还是我真的在这里遗漏了什么?如果我没有遗漏任何东西,那么我真的被迫在这种情况下暴露我的实现吗?[... *咳嗽*看起来更像是编译器的缺陷,老实说*咳嗽*...]ML_GLOBAL
答:
原来是虚惊一场。尽管如此,我花了几个小时才终于想起为什么这个符号对消费者来说可能是不可见的。这真的是微不足道的,但我觉得把它发布在这里,供碰巧有相同设置的未来访问者使用。基本上,如果您使用链接器脚本 [1] 或(纯)版本脚本 [2](使用链接器选项指定),请不要忘记为这些基于第三方的符号(或您的任何符号)设置可见性。就我而言,我最初有以下几点:--version-script
global
tpl::foo*
{
global:
extern "C++" {
ml::*;
typeinfo*for?ml::*;
vtable*for?ml::*;
};
local:
extern "C++" {
*;
};
};
我显然必须改变什么
{
global:
extern "C++" {
tpl::foo*;
ml::*;
typeinfo*for?ml::*;
vtable*for?ml::*;
};
local:
extern "C++" {
*;
};
};
为了正确链接所有内容并获得预期的结果。
希望这对您有所帮助和问候。
奖金
不过,好奇的读者可能会问,“当已经有应该这样做的 和 选项时,为什么要结合显式可见性属性和链接器/版本脚本来控制符号的可见性?-fvisibility=hidden
-fvisibility-inlines-hidden
答案是它们当然有,我确实用它们来构建我的共享库。但是,有一个问题。通常的做法是将共享库使用的一些内部库(私下)静态地链接到该库中,主要是为了完全隐藏此类依赖关系(但请记住,共享库附带的头文件也应该正确设计以实现这一点)。好处是显而易见的:干净可控的 ABI,并缩短了共享库使用者的编译时间。
以 Boost 为例,它是此类用例最广泛的候选者。将 Boost 中所有大量模板化的代码私下封装到您的共享库中,并从 ABI 中删除任何 Boost 符号,将大大减少共享库使用者的接口污染和编译时间,这还不包括您的软件组件看起来也经过专业开发的事实。
无论如何,事实证明,除非那些你想链接到你的共享库的静态库本身也是用 和 选项构建的(这将是一个荒谬的期望,因为没有人会默认分发带有隐藏接口符号的静态库,因为它违背了它们的目的),它们的符号将不可避免地仍然是可见的(例如, 通过 ),无论您是否使用这些选项构建共享库本身。也就是说,在这种情况下,您有两种方法可以解决它:-fvisibility=hidden
-fvisibility-inlines-hidden
nm -CD <shared-library>
- 使用 和 选项手动重建这些静态库(您的共享库依赖项),鉴于它们潜在的第三方来源,这显然并不总是可行/实用的。
-fvisibility=hidden
-fvisibility-inlines-hidden
- 使用链接器/版本脚本(如上所述)在链接时提供,以指示链接器从共享库中强制导出/隐藏正确的符号。
评论