windows DLL 导出模板基类的静态成员
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7848865/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
DLL-Exporting static members of template base class
提问by Joris Timmermans
Within a DLL I have an exported non-template class with a template base class. This template base class has a static member variable. I use the static base member in an executable that links to the DLL with the exported non-template class.
在 DLL 中,我有一个带有模板基类的导出非模板类。这个模板基类有一个静态成员变量。我在链接到带有导出的非模板类的 DLL 的可执行文件中使用静态基成员。
In many scenarios I get unresolved external symbols or complaints about inconsistent linkage. I have found one scenario that works, but it seems to be kludgey so I'm wondering if there is a better way and if that better way might also point to deficiencies in VS2010 SP1's C++ compiler/linker.
在许多情况下,我会收到未解决的外部符号或有关不一致链接的投诉。我发现了一种可行的方案,但它似乎很笨拙,所以我想知道是否有更好的方法,以及这种更好的方法是否也可能指向 VS2010 SP1 的 C++ 编译器/链接器的缺陷。
This is the minimal scenario of the DLL that I could distill - I don't think I could remove anything here without breaking the scenario.
这是我可以提取的 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>;
Then the user of the DLL
那么DLL的用户
#include <header.h>
#include <iostream>
int main(void) {
MyClass c;
std::cout << c.g_initial_value;
return 0;
}
采纳答案by Alan Baljeu
In C++ generally, when a plain class has a static member, it should be declared in the header, but instantiated in a source file. To do otherwise would cause too many instances of the static class member to be created.
通常在 C++ 中,当一个普通类有一个静态成员时,它应该在头文件中声明,但在源文件中实例化。否则会导致创建过多的静态类成员实例。
Templates are kind of the same way, except the compiler has some magic for templates that it doesn't have for non-templates. Specifically, it magically eliminates duplicate instances of a template instantiation during the linking phase of a build.
模板有点类似,除了编译器对模板有一些魔法,而对于非模板则没有。具体来说,它在构建的链接阶段神奇地消除了模板实例化的重复实例。
This is the source of your problem: The stuff inside the _MYDLL portion is automatically instantiated by every source file that includes this template and also makes TBaseClass objects. Then the linker automatically eliminates duplicates.
这是您的问题的根源:_MYDLL 部分中的内容由包含此模板的每个源文件自动实例化,并且还生成 TBaseClass 对象。然后链接器会自动消除重复项。
Trouble is, you have two links: the DLL link and the client link. Both will make TBaseClass instantiations, and both will make those g_initial_value objects.
问题是,您有两个链接:DLL 链接和客户端链接。两者都会使 TBaseClass 实例化,并且都会产生那些 g_initial_value 对象。
To solve this: Move the stuff in the _MYDLL conditional into the CPP file, so the client won't get instructions to build the instance itself.
要解决这个问题:将 _MYDLL 条件中的内容移动到 CPP 文件中,这样客户端就不会得到构建实例本身的指令。
回答by Miguel
The fact that you are using your template class from both the DLL and the EXE make things more confusing, but still, it can work.
您同时使用来自 DLL 和 EXE 的模板类这一事实使事情变得更加混乱,但它仍然可以工作。
First of all, you should implement your template base class entirely in the header file. If you don't know why, then make sure you read the accepted answer to this question.
首先,您应该完全在头文件中实现您的模板基类。如果您不知道为什么,请确保您阅读了此问题的已接受答案。
Now let's forget about templates and DLLs, and consider a much simpler case. Let's say you have class C, with a static member. You would normally code this class in this way:
现在让我们忘记模板和 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;
Nothing odd or complicated here. Now consider what would happen if you move the static declaration to the header file. If there is only one source file that includes the header, then everything will work just fine. But if two or more source files included this header, then this static member will be defined multiple times, and the linker will not like that.
这里没有什么奇怪或复杂的。现在考虑如果将静态声明移动到头文件会发生什么。如果只有一个包含标题的源文件,那么一切都会正常进行。但是如果两个或多个源文件包含这个头文件,那么这个静态成员将被定义多次,链接器不会喜欢那样。
The same concept applies to a template class. Your #ifdef _MYDLL
hack only works because from the DLL you are including this header file only once. But the moment you include this file from another source file you'll start getting linker errors on the DLL! So I completely agree with you, this is not a good solution.
相同的概念适用于模板类。您的#ifdef _MYDLL
hack 仅有效,因为从 DLL 中您只包含此头文件一次。但是当您从另一个源文件中包含此文件时,您将开始在 DLL 上收到链接器错误!所以我完全同意你的看法,这不是一个好的解决方案。
I think one thing that complicates your setup is that you allow both the DLL and the EXE to instantiate this template base class. I think you would have a much cleaner solution if you find an "owner" for each instantiation of the template class. Following your code example, let's replace MyClass with MyDLLClass and MyEXEClass. Then you could make this work like this:
我认为使您的设置复杂化的一件事是您允许 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;
}
I hope this makes sense.
我希望这是有道理的。
回答by langdead
In fact, the exported class's base class is exported too if the base class is a template class, but not true on the contrary. Please refer to http://www.codesynthesis.com/~boris/blog/2010/01/18/dll-export-cxx-templates/
事实上,如果基类是模板类,导出类的基类也会被导出,反之则不然。请参考http://www.codesynthesis.com/~boris/blog/2010/01/18/dll-export-cxx-templates/
For your specific question, I suggest you define a static method in the base template which returns a variable(pointer?) of interest. Then only one definition will happen across multiple dlls or exe which depends on your library.
对于您的具体问题,我建议您在基本模板中定义一个静态方法,该方法返回一个感兴趣的变量(指针?)。然后只有一个定义会发生在多个 dll 或 exe 中,这取决于您的库。
回答by Vyacheslav Lanovets
While I would suggest using your current approach, actually it's possible to avoid #ifdef by using older syntax for exporting templates from a DLL. All this goes to the DLL's header file:
虽然我建议使用您当前的方法,但实际上可以通过使用旧语法从 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>
{
};
At runtime the address of g_initial_value in the client code lies within DLL's address space so it seems to work correctly.
在运行时,客户端代码中 g_initial_value 的地址位于 DLL 的地址空间内,因此它似乎可以正常工作。