DLL-导出模板基类的静态成员

时间:2011-10-21 11:40:20

标签: c++ windows visual-studio-2010 templates

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

在许多情况下,我得到了未解决的外部符号或有关不一致链接的抱怨。我找到了一个可行的方案,但它似乎是kludgey所以我想知道是否有更好的方法,如果更好的方式也可能指向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;
}

4 个答案:

答案 0 :(得分:4)

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

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

这是您的问题的根源:_MYDLL部分中的内容由包含此模板的每个源文件自动实例化,并且还生成TBaseClass对象。然后链接器会自动消除重复项。

麻烦的是,你有两个链接:DLL链接和客户端链接。两者都将进行TBaseClass实例化,并且两者都将生成那些g_initial_value对象。

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

答案 1 :(得分:2)

您正在使用DLL和EXE中的模板类这一事实使事情变得更加混乱,但仍然可以使用。

首先,您应该完全在头文件中实现模板基类。如果您不知道原因,请务必阅读this question的接受答案。

现在让我们忘记模板和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黑客只能运行,因为从DLL中只包含此头文件一次。但是,当您从另一个源文件中包含此文件时,您将开始在DLL上获取链接器错误!所以我完全赞同你,这不是一个好的解决方案。

我认为使您的设置复杂化的一件事是您允许DLL和EXE实例化此模板基类。如果你为模板类的每个实例化找到一个“所有者”,我想你会有一个更清洁的解决方案。按照你的代码示例,让我们用MyDLLClass和MyEXEClass替换MyClass。那么你可以这样做:

// 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;
}

我希望这是有道理的。

答案 2 :(得分:1)

实际上,如果基类是模板类,导出的类的基类也会被导出,但事实并非如此。请参阅http://www.codesynthesis.com/~boris/blog/2010/01/18/dll-export-cxx-templates/

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

答案 3 :(得分:0)

虽然我建议使用您当前的方法,但实际上可以通过使用较旧的语法从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的地址空间内,因此它似乎正常工作。