导出模板基类的静态成员
DLL-Exporting static members of template base class
在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的地址空间内,因此它似乎可以正常工作。
- 使用静态成员声明类时遇到问题
- 使用派生类中的静态成员而不是基类
- 从相同类型的静态成员进行类内初始化
- 具有静态成员的类的前向声明
- 枚举不是非静态数据成员或类的基类
- boost::bind 无法绑定到纯虚拟基类中定义的非静态函数模板成员类型
- 在没有显式作用域的情况下无法访问模板基类的静态成员
- C++ - 在派生类中静态初始化受基类保护的成员变量
- 为了在测试中调用destuructor(用于测试),静态铸造基类有多么可怕
- 访问指定为私有的基类的公共静态成员
- 如果基类是公开继承的,那么基类的公共静态函数是否会成为派生类的成员函数
- 指向成员的 C++ 指针(指向成员基类的指针)
- 成员初始值设定项"超类"不命名非静态数据成员或基类
- 具有静态成员的类包含C++中的静态成员
- 模板基类的静态成员不会导出到共享库
- C++-Do派生类继承基类的静态成员
- 在包含其自身类型的静态成员的类中使用 __declspec(dllimport)
- 为什么静态成员的类内初始化会违反 ODR
- 命名空间中的函数重载(与具有静态成员的类相比)是个坏主意吗
- 如何在c++中将模板成员函数的实参转发给成员基类指针