c++在写模版函数时(template<class T>之类的),头文件不能与cpp文件分离。这就意味者,你头文件定义的含模版的地方必须在头文件中实现,没用模版定义的地方可以放在cpp中实现。
否则,将产生错误信息:
严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK2019 无法解析的外部符号 "public: void __thiscall XXX
有什么办法可以是西安模板类的声明与实现分离(模板实例化)问题吗?
模板实例化有什么好处?
- 减少编译时间。
- 使得类定义与实现分离。
减少编译时间
一般来说,如果你的项目没有混合使用 C 和 C++ 语言,那么你使用 .h 和 .cpp 是没有问题的。否则你将C和C++的头文件进行分离,因为通常我们把C和C++分离编译,再统一链接。
- 函数经过编译系统的翻译成汇编,函数名对应着汇编标号。 因为C编译函数名与得到的汇编代号基本一样,如:fun()=>_fun, main=>_main ;但是C++中函数名与得到的汇编代号有比较大的差别。 如:由于函数重载,函数名一样,但汇编代号绝对不能一样。
- 为了区分,编译器会把函数名和参数类型合在一起作为汇编代号, 这样就解决了重载问题。具体如何把函数名和参数类型合在一起, 要看编译器的帮助说明了。
- 这样一来,如果C++调用C,如fun(),则调用名就不是C的翻译结果_fun, 而是带有参数信息的一个名字,因此就不能调用到fun(),为了解决 这个问题,加上extern "C"表示该函数的调用规则是C的规则,则调用 时就不使用C++规则的带有参数信息的名字,而是_fun,从而达到调用 C函数的目的。
//header.hpp
#pragma once
extern "C"
{
#include "header.h" // 真正的 C header
}
好的,说会正事 ~
//foo.hpp (.h和.hpp就如同.c和.cpp似的)
template<typename T>
struct Foo {
void f();
};
//foo.cpp
template<typename T>
void Foo<T>::f() {}
template class Foo<T1>;
template class Foo<T2>;
在标头中声明实例化:
extern template class A<int>;
并在一个源文件中定义它:
template class A<int>;
现在它只会被实例化一次,而不是每个翻译单元,这可能会加快速度。
首先需要知道的是把模版类的定义和实现分开写了,编译将会出错。
C++中每一个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的。只有模板被真正使用的时候,编译器才知道,模板套用的是什么类型,应该分配多少空间。这也就是模板类为什么只是称之为模板,而不是泛型的缘故。
既然是在编译的时候,根据套用的不同类型进行编译,那么,套用不同类型的模板类实际上就是两个不同的类型,也就是说,stack<int>和stack<char>是两个不同的数据类型,他们共同的成员函数也不是同一个函数,只不过具有相似的功能罢了。
如上图所示,很简短的六行代码,用的是STL里面的stack,stack<int>和stack<char>的默认构造函数和push函数的入口地址是不一样的,而不同的stack<int>对象相同的函数入口地址是一样的,这个也反映了模板类在套用不同类型以后,会被编译出不同代码的现象。
所以模板类的实现,脱离具体的使用,是无法单独的编译的;把声明和实现分开的做法也是不可取的,必须把实现全部写在头文件里面。为了清晰,实现可以不写在class后面的花括号里面,可以写在class的外面。
实现分离
显式实例化允许您创建模板化类或函数的实例化,而无需在代码中实际使用它。因为在创建使用模板进行分发的库(.lib)文件时这很有用,所以未将实例化的模板定义放入对象(.obj)文件中。
(例如,libstdc ++包含std::basic_string<char,char_traits<char>,allocator<char> >
(是std::string
)的显式实例化,所以每次使用函数时std::string
,都不需要将相同的函数代码复制到对象。编译器只需要将它们引用(链接)到libstdc ++。)
显示实例化时,当你是只有一个 cpp 的情况.可能没有什么问题。但 如果有多个 cpp 文件再使用这个模版, 你必须把它放在头文件里, 然后每个 cpp 都要 #include 这个头文件. 显示实例化之后头文件里只需要声明, 然后在其中一个 cpp 里面实现并显示实例化, 其它的 cpp 就可以直接用了.
在 C++ 中定义普通的类的时候,比较清晰和普遍的做法是在 .h 文件中声明类的接口,如果有 inline 函数也一并放在 .h 中的 class 关键字中。然后在 .cpp 文件中实现 .h 中声明的接口。
在定义模板类时,如果要将接口与实现分离会略有不同。如果把模板类的实现像普通类一样放在 .cpp 文件中链接器会报错。
有两个方法可以实现模板类的接口和实现在文件中的分离:
一个前提
“类模板的成员函数是一个普通函数。但是类模板的每个实例都有其自己版本的成员函数。因此类模板的成员函数具有和模板相同的模板参数。因此定义在类模板之外的成员函数就必须以关键字 template 开始,后接类模板参数列表。”
——《C++ Primer》中文版,第五版,P585
使用 .tpp 文件实现类模板的接口与实现的文件分离
比如说有这样一个模板类,这是它的接口:
template <typename Node>
class TestTemplate{
public:
TestTemplate(Node node):
data(node) { }
Node data;
void print();
};
这是它的实现:
template <typename node>
void TestTemplate<node>::print(){
std::cout << "TestTemplate " << data << std::endl;
}
如果把它们分别放在 .h 和 .cpp 文件中,链接器会报错,提示找不到实现。
在 .h 文件中模板类的实现下加这一句:
#include "TestTemplate.tpp"
然后把实现放在名为 TestTemplate.tpp 文件中,即可。
使用显式声明实现类模板的接口与实现的文件分离
假设上面那个类的接口与实现分别放在了 .h 和 .cpp 文件中。然后在 .cpp 文件中显式的声明要使用的模板类实例,比如:
template class TestTemplate<int>;
然后,使用 TestTemplate<int> 也可以通过编译链接,但是只能使用已经显式声明的模板类实例。比如如果还要使用 TestTemplate<float>,就要这样:
template class TestTemplate<int>;
template class TestTemplate<float>;
就是说只能只用已经显式声明过的模板类实例。
如果您定义了一个模板类,您只想使用几个显式类型。
将模板声明放在头文件中就像普通类一样。
将模板定义放在源文件中,就像普通类一样。
然后,在源文件的末尾,显式地仅实例化您想要可用的版本。
我的实验:
//StringAdapter.h
#pragma once
#include <iostream>
#include <cstring>
using namespace std;
template<typename T>
class StringAdapter
{
public:
StringAdapter(T* data);
void doAdapterStuff();
private:
T* m_data;
};
#include "StringAdapter.tpp"
// StringAdapter.tpp
#include "StringAdapter.h"
template<typename T>
StringAdapter<T>::StringAdapter(T* data) : m_data(data)
{
cout << m_data << endl;
}
template<typename T>
void StringAdapter<T>::doAdapterStuff()
{
/* Manipulate a string */
cout << "hello" << endl;
}
// main.cpp
#include "StringAdapter.h"
// Note: Main can not see the definition of the template from here (just the declaration)
// So it relies on the explicit instantiation to make sure it links.
int main()
{
StringAdapter<const char> x("hi There");
x.doAdapterStuff();
}
stackoverflow 中愚蠢的例子Silly example::
// StringAdapter.h
template<typename T>
class StringAdapter
{
public:
StringAdapter(T* data);
void doAdapterStuff();
private:
std::basic_string<T> m_data;
};
typedef StringAdapter<char> StrAdapter;
typedef StringAdapter<wchar_t> WStrAdapter;
source:
// StringAdapter.cpp
#include "StringAdapter.h"
template<typename T>
StringAdapter<T>::StringAdapter(T* data)
:m_data(data)
{}
template<typename T>
void StringAdapter<T>::doAdapterStuff()
{
/* Manipulate a string */
}
// Explicitly instantiate only the classes you want to be defined.
// In this case I only want the template to work with characters but
// I want to support both char and wchar_t with the same code.
template class StringAdapter<char>;
template class StringAdapter<wchar_t>;
Main:
#include "StringAdapter.h"
// Note: Main can not see the definition of the template from here (just the declaration)
// So it relies on the explicit instantiation to make sure it links.
int main()
{
StrAdapter x("hi There");
x.doAdapterStuff();
}
一些有趣的小知识
《C++ Template》第六章讲过这个问题
组织模板代码有三种方式
1.包含模型(常规写法 将实现写在头文件中)
2.显式实例化(实现写在cpp文件中,使用template class语法进行显式实例化)
3.分离模型(使用C++ export关键字声明导出)
第三种方式理论最优,但是实际从C++标准提出之后主流编译器没有支持过,并且在最新的C++11标准中已经废除此特性,export关键字保留待用。
那么实际上能够使用的实现分离也就只有显式实例化
比较有意思的是,《C++ Template》书中作者建议代码为分离模型做准备,等待编译器支持之后替换,没想到最终这个特性被C++标准废弃了。
参考:https://www.zhihu.com/question/20630104
https://bbs.csdn.net/topics/380250382
http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file
https://stackoverflow.com/questions/2351148/explicit-instantiation-when-is-it-used
google搜索:Explicit template instantiation benefits