导出模板基类的静态成员

DLL-Exporting static members of template base class

本文关键字:静态成员 基类      更新时间:2023-10-16

在DLL中,我导出了一个带有模板基类的非模板类。这个模板基类有一个静态成员变量。我在可执行文件中使用静态基成员,该成员通过导出的非模板类链接到DLL。

在许多情况下,我得到无法解决的外部符号或关于不一致链接的抱怨。我发现了一个可行的方案,但它似乎是笨拙的,所以我想知道是否有更好的方法,如果更好的方法也可能指出VS2010 SP1的c++编译器/链接器的缺陷。

这是我可以提取的DLL的最小场景-我不认为我可以在不破坏场景的情况下删除这里的任何东西。

// Header file
template<typename T>
class _MYDLL_EXPORTS TBaseClass
  {
  public:
    static const double g_initial_value;
  };
class _MYDLL_EXPORTS MyClass : public TBaseClass<MyClass>
  {    
  };
// Kludge: use this code only when building the DLL, not when including
// from the DLL's client
#ifdef _MYDLL
  template<typename T>
  const double TBaseClass<T>::g_initial_value = 1e-5;
#endif

// CPP file
#include "header.h"
// Explicit instantiation of the template for the correct parameter.
template class TBaseClass<MyClass>;

然后DLL的用户

#include <header.h>  
#include <iostream>
int main(void) {
 MyClass c;
 std::cout << c.g_initial_value;
return 0;
}

在c++中,一般来说,当一个普通类有静态成员时,它应该在头文件中声明,但在源文件中实例化。否则将导致创建太多静态类成员的实例。

模板也是一样的,除了编译器对模板有一些它对非模板没有的魔力。具体来说,它在构建的链接阶段神奇地消除了模板实例化的重复实例。

这是你的问题的根源:_MYDLL部分内的东西是自动实例化的每个源文件,包括这个模板,也使TBaseClass对象。然后链接器自动删除重复项。

问题是,您有两个链接:DLL链接和客户端链接。它们都将生成TBaseClass实例化,并且都将生成g_initial_value对象。

要解决这个问题:将_MYDLL条件中的内容移到CPP文件中,这样客户端就不会得到构建实例本身的指令。

事实上,你正在使用你的模板类从DLL和EXE使事情更加混乱,但仍然,它可以工作。

首先,你应该完全在头文件中实现你的模板基类。如果你不知道为什么,那么一定要看看这个问题的公认答案。

现在让我们忘记模板和dll,考虑一个更简单的例子。假设你有一个类C,它有一个静态成员。您通常可以这样编写该类:

// C.h file
class C {
public:
    static const double g_initial_value;
};
// C.cpp file
const double C::g_initial_value = 1e-5;

这里没有什么奇怪或复杂的。现在考虑如果将静态声明移动到头文件中会发生什么。如果只有一个包含头文件的源文件,那么一切都将正常工作。但是,如果两个或多个源文件包含这个头文件,那么这个静态成员将被定义多次,而链接器将不喜欢这样。

同样的概念也适用于模板类。您的#ifdef _MYDLL hack只工作,因为从DLL中您只包含一次头文件。但是,当您从另一个源文件包含此文件时,您将开始在DLL上获得链接器错误!所以我完全同意你的看法,这不是一个好的解决方案。

我认为使你的设置复杂化的一件事是,你允许DLL和EXE实例化这个模板基类。我认为,如果您为模板类的每个实例化找到一个"所有者",您将有一个更清晰的解决方案。按照代码示例,让我们将MyClass替换为MyDLLClass和myexecclass。然后你可以这样做:

// dll.h
template<typename T>
class _MYDLL_EXPORTS TBaseClass
  {
  public:
    static const double g_initial_value;
  };
class _MYDLL_EXPORTS MyDLLClass : public TBaseClass<MyDLLClass>
  {    
  };
// dll.cpp
#include "dll.h"
// this file "owns" MyDLLClass so the static is defined here
template<> const double TBaseClass<MyDLLClass>::g_initial_value = 1e-5;
// exe.h
#include "dll.h"
class MyEXEClass : public TBaseClass<MyEXEClass>
  {    
  };
// exe.cpp
#include "exe.h"
#include <iostream>
// this file "owns" MyEXEClass so the static is defined here
template<> const double TBaseClass<MyEXEClass>::g_initial_value = 1e-5;
int main(int argc, char* argv[])
{
    MyDLLClass dll;
    MyEXEClass exe;
    std::cout << dll.g_initial_value;
    std::cout << exe.g_initial_value;
}

实际上,如果导出的类的基类是模板类,则该基类也会导出,反之则不然。请参考http://www.codesynthesis.com/~boris/blog/2010/01/18/dll-export-cxx-templates/

对于您的具体问题,我建议您在基模板中定义一个静态方法,该方法返回感兴趣的变量(指针?)。然后,只有一个定义将在多个dll或exe中发生,这取决于您的库。

虽然我建议使用您当前的方法,但实际上可以通过使用从DLL导出模板的旧语法来避免#ifdef。所有这些都进入DLL的头文件:

#pragma once
#ifdef _MYDLL
#define _MYDLL_EXPORTS __declspec(dllexport)
#else
#define _MYDLL_EXPORTS __declspec(dllimport)
#endif
template<typename T> 
class _MYDLL_EXPORTS TBaseClass // _MYDLL_EXPORTS is not needed here
{ 
public: 
    static double g_initial_value; 
}; 
template<typename T> 
double TBaseClass<T>::g_initial_value = 1e-5; 
class MyClass;
template class _MYDLL_EXPORTS TBaseClass<MyClass>;
class _MYDLL_EXPORTS MyClass : public TBaseClass<MyClass> 
{     
}; 

在运行时,客户端代码中的g_initial_value的地址位于DLL的地址空间内,因此它似乎可以正常工作。