C++ 模板类的声明与实现分离问题(模板实例化)

模板实例化有什么好处?

  1. 减少编译时间。
  2. 使得类定义与实现分离。

减少编译时间

//foo.hpp
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>是两个不同的数据类型,他们共同的成员函数也不是同一个函数,只不过具有相似的功能罢了。

C++ 模板类的声明与实现分离问题(模板实例化)

如上图所示,很简短的六行代码,用的是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>;

就是说只能只用已经显式声明过的模板类实例。

 

如果您定义了一个模板类,您只想使用几个显式类型。

将模板声明放在头文件中就像普通类一样。

将模板定义放在源文件中,就像普通类一样。

然后,在源文件的末尾,显式地仅实例化您想要可用的版本。

愚蠢的例子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.****.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