假设有一个第三方库,其头文件中包含以下内容:
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
的 undefined reference 错误象征。事实上,正在运行 nm -CD
在生成的共享库上没有显示 tpl::foo<::ml::xxx, void>
的痕迹符号。
到目前为止,我尝试过在放置位置上使用不同的组合 ML_GLOBAL
(例如,关于显式模板实例化本身,GCC 明显提示的与 Clang 不同)以及有/没有第二个模板参数 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
选项应该这样做,为什么还要结合显式可见性属性和链接器/版本脚本来控制符号的可见性?”。
答案是它们当然可以,而且我确实使用它们来构建我的共享库。然而,有一个问题。静态链接共享库使用的一些内部库(私有(private))是一种常见的做法(进入该库),主要是为了完全隐藏此类依赖关系(但请记住,共享库附带的头文件也应该是正确设计以实现这一点)。好处是显而易见的:干净且可控的 ABI 并减少了共享库使用者的编译时间。
以 Boost 为例,它是此类用例最广泛的候选者。将 Boost 中的所有高度模板化代码私下封装到您的共享库中,并从 ABI 中消除任何 Boost 符号,将大大减少共享库使用者的接口(interface)污染和编译时间,更不用说您的软件组件看起来也是经过专业开发的。
无论如何,事实证明,除非您想要链接到共享库的那些静态库本身也是使用 -fvisibility=hidden
和 -fvisibility-inlines-hidden
选项构建的(这将是一个荒谬的期望,因为没有人会去默认情况下分发带有隐藏接口(interface)符号的静态库,因为这违背了它们的目的),它们的符号将不可避免地仍然可见(例如,通过 nm -CD <shared-library>
),无论您使用这些符号构建共享库本身选项。也就是说,在这种情况下,您有两种选择来解决它:
- 使用
-fvisibility=hidden
和-fvisibility-inlines-hidden
选项手动重建这些静态库(您的共享库依赖项),鉴于其潜在的第三方来源,这显然并不总是可能/不切实际的。 - 使用链接器/版本脚本(如上面所做的那样)在链接时提供,以指示链接器从共享库中强制导出/隐藏正确的符号。
关于c++ - 共享库: Undefined Reference with Partial Template Specialization and Explicit Template Instantiation,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42342215/