2

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

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&);
};
}

whereML_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>

到目前为止,我尝试过的是关于放置位置的不同组合ML_GLOBAL(例如,在显式模板实例化本身上,关于 GCC 明显抱怨不像 Clang)和有/没有第二个模板参数void

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

4

1 回答 1

3

事实证明这是一场虚惊。尽管如此,我还是花了几个小时才终于想起为什么这个符号对消费者来说是看不见的。这真的很微不足道,但我想把它贴在这里给碰巧有相同设置的未来访问者。基本上,如果您使用链接器脚本[ 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=hiddenand选项时,为什么还要结合显式可见性属性和链接器/版本脚本来控制符号的可见性?”。-fvisibility-inlines-hidden

答案是它们当然可以,而且我确实使用它们来构建我的共享库。但是,有一个问题。将共享库使用的一些内部库(私有)静态链接(到该库中)是一种常见的做法,主要是为了完全隐藏这种依赖关系(但请记住,共享库随附的头文件也应该是正确设计来实现这一点)。好处很明显:干净且可控的 ABI 并减少了共享库使用者的编译时间。

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

无论如何,事实证明,除非您要链接到共享库的那些静态库本身也使用-fvisibility=hiddenand-fvisibility-inlines-hidden选项构建(这将是一个荒谬的期望,因为没有人会分发带有隐藏接口符号的静态库默认情况下,因为它违背了它们的目的),它们的符号将不可避免地仍然可见(例如,通过nm -CD <shared-library>),无论您使用这些选项构建共享库本身。也就是说,在这种情况下,您有两种选择来解决它:

  1. -fvisibility=hidden使用and选项手动重建这些静态库(您的共享库依赖项),-fvisibility-inlines-hidden鉴于它们潜在的第三方来源,这显然并不总是可能/实用的。
  2. 使用链接器/版本脚本(如上面所做的那样)在链接时提供,以指示链接器从共享库中强制导出/隐藏正确的符号。
于 2017-02-26T22:49:18.737 回答