c++ - 具有跨多个 DLL/DSO 的静态成员的模板类

标签 c++ templates dll static shared-libraries

关于包含静态成员变量的 C++ 模板类以及从动态库或共享对象中导出它们的问题很多。但这一个更深一些:如果有多个共享对象,每个共享对象都有自己的一组实例化,但可能使用来自另一个共享对象的实例化,该怎么办?

考虑以下示例代码:

/* file: common.h */

#include <stdio.h>
#define PRINT fprintf (stderr, "(template %d) %d -> %d\n", parameter, data, new_data)

template <int parameter>
class SharedClass
{
    static int data;
public:
    static void Set(int new_data) { PRINT; data = new_data; }
};

template <int parameter>
int SharedClass<parameter>::data = parameter;


/* file: library1.h */

extern template class SharedClass<1>;
void Library1Function();


/* file: library1.cpp */

#include "common.h"
#include "library1.h"
#include "library2.h"

template class SharedClass<1>;

void Library1Function()
{
    SharedClass<1>::Set (100);
    SharedClass<2>::Set (200);
}


/* file: library2.h */

extern template class SharedClass<2>;
void Library2Function();


/* file: library2.cpp */

#include "common.h"
#include "library1.h"
#include "library2.h"

template class SharedClass<2>;

void Library2Function()
{
    SharedClass<1>::Set (1000);
    SharedClass<2>::Set (2000);
}


/* file: main.cpp */

#include "common.h"
#include "library1.h"
#include "library2.h"

int main()
{
    Library1Function();
    Library2Function();
    SharedClass<1>::Set (-1);
    SharedClass<2>::Set (-2);
}

然后假设我们使用 GCC 构建了两个库和一个应用程序:

$ g++ -fPIC -fvisibility=default -shared library1.cpp -o lib1.so
$ g++ -fPIC -fvisibility=default -shared library2.cpp -o lib2.so
$ g++ -fvisibility=default main.cpp -o main -Wl,-rpath=. -L. -l1 -l2

然后运行可执行文件,我们将得到如下结果:

$ ./main
(template 1) 1 -> 100
(template 2) 2 -> 200
(template 1) 100 -> 1000
(template 2) 200 -> 2000
(template 1) 1000 -> -1
(template 2) 2000 -> -2

这意味着库和可执行文件都访问相同的每个模板静态存储。
如果我们在二进制文件上运行“nm -C”,我们会看到每个静态成员只定义一次并且在相应的库中:

$ nm -C -A *.so main | grep ::data
lib1.so:0000000000001c30 u SharedClass<1>::data
lib2.so:0000000000001c30 u SharedClass<2>::data

但是我有一些问题。

  1. 为什么,如果我们从两个 header 中删除 extern template class ...,我们将看到静态成员存在于每个二进制文件中,但测试应用程序将继续工作正常吗?

    $ nm -C -A *.so main | grep ::data
    lib1.so:0000000000001c90 u SharedClass<1>::data
    lib1.so:0000000000001c94 u SharedClass<2>::data
    lib2.so:0000000000001c94 u SharedClass<1>::data
    lib2.so:0000000000001c90 u SharedClass<2>::data
    main:0000000000401e48 u SharedClass<1>::data
    main:0000000000401e4c u SharedClass<2>::data
    
  2. 是否可以在 MSVC 下构建它?
    或者,更具体地说,如何处理 __declspec(dllexport)__declspec(dllimport) 以导出一些实例化,并导入一些实例化?

  3. 最后:这是未定义行为的一个例子吗?

最佳答案

要回答第 1 点:当动态链接器解析符号时,它使用一个模块列表来链接。首先检查加载的第一个模块,然后检查第二个,依此类推。

IIRC,当 data member 在 main、lib1.so 和 lib2.so 中使用,这仍然被视为动态符号引用,即使该成员是在同一模块中声明的。因此,当链接器在您运行程序时解析符号时,所有三个模块都使用 data 结束。仅在三个模块之一中实现成员:以首先加载的为准。其他两对仍加载到内存中,但未使用。

(在所有三个模块中尝试 std::cout << &(SharedClass<n>::data) << std::endl;所有六种情况下打印的地址都应该相同。)

要回答第 3 点,我认为这种行为根本不是未定义的。发生什么完全取决于您系统的动态链接器,但我不知道有哪个链接器不能以完全相同的方式处理这种情况。

我不能说第 2 点,因为我没有太多的 MSVC 经验。

关于c++ - 具有跨多个 DLL/DSO 的静态成员的模板类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10986757/

相关文章:

c++ - 如何在可能不存在的目录中创建文件?

c++ - boost make_shared 的用例

c++ - 如何处理模板中的成员变量

c# - 在 Visual Studio 命令提示符 2010 中构建 c# dll 时如何指定 .net 目标框架?

c++ - 可移植地链接外部库?

仅 C++ 前端编译器(将 C++ 转换为 C)

c++ - 返回对象作为函数结果

c++ - 如何在类主体中调用模板函数?

java - Eclipse修改新建类对话框中的默认Main方法

C++ 库编译但不像以前的版本那样工作