当前位置:   article > 正文

C++ 模板类的声明与实现分离问题(模板实例化)_c++模板如何解决类在声明后的问题

c++模板如何解决类在声明后的问题

c++在写模版函数时(template<class T>之类的),头文件不能与cpp文件分离。这就意味者,你头文件定义的含模版的地方必须在头文件中实现,没用模版定义的地方可以放在cpp中实现。

否则,将产生错误信息:

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK2019 无法解析的外部符号 "public: void __thiscall XXX

有什么办法可以是西安模板类的声明与实现分离(模板实例化)问题吗?

 

模板实例化有什么好处?

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

减少编译时间

一般来说,如果你的项目没有混合使用 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函数的目的。
  1. //header.hpp
  2. #pragma once
  3. extern "C"
  4. {
  5. #include "header.h" // 真正的 C header
  6. }

好的,说会正事 ~

  1. //foo.hpp (.h和.hpp就如同.c和.cpp似的)
  2. template<typename T>
  3. struct Foo {
  4. void f();
  5. };
  1. //foo.cpp
  2. template<typename T>
  3. void Foo<T>::f() {}
  4. template class Foo<T1>;
  5. 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 文件实现类模板的接口与实现的文件分离

比如说有这样一个模板类,这是它的接口:

  1. template <typename Node>
  2. class TestTemplate{
  3. public:
  4. TestTemplate(Node node):
  5. data(node) { }
  6. Node data;
  7. void print();
  8. };

这是它的实现:

  1. template <typename node>
  2. void TestTemplate<node>::print(){
  3. std::cout << "TestTemplate " << data << std::endl;
  4. }

如果把它们分别放在 .h 和 .cpp 文件中,链接器会报错,提示找不到实现。

在 .h 文件中模板类的实现下加这一句:

#include "TestTemplate.tpp"

然后把实现放在名为 TestTemplate.tpp 文件中,即可。

使用显式声明实现类模板的接口与实现的文件分离

假设上面那个类的接口与实现分别放在了 .h 和 .cpp 文件中。然后在 .cpp 文件中显式的声明要使用的模板类实例,比如:

template class TestTemplate<int>;

然后,使用 TestTemplate<int> 也可以通过编译链接,但是只能使用已经显式声明的模板类实例。比如如果还要使用 TestTemplate<float>,就要这样:

  1. template class TestTemplate<int>;
  2. template class TestTemplate<float>;

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

 

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

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

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

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

 

我的实验:

  1. //StringAdapter.h
  2. #pragma once
  3. #include <iostream>
  4. #include <cstring>
  5. using namespace std;
  6. template<typename T>
  7. class StringAdapter
  8. {
  9. public:
  10. StringAdapter(T* data);
  11. void doAdapterStuff();
  12. private:
  13. T* m_data;
  14. };
  15. #include "StringAdapter.tpp"
  16. // StringAdapter.tpp
  17. #include "StringAdapter.h"
  18. template<typename T>
  19. StringAdapter<T>::StringAdapter(T* data) : m_data(data)
  20. {
  21. cout << m_data << endl;
  22. }
  23. template<typename T>
  24. void StringAdapter<T>::doAdapterStuff()
  25. {
  26. /* Manipulate a string */
  27. cout << "hello" << endl;
  28. }
  29. // main.cpp
  30. #include "StringAdapter.h"
  31. // Note: Main can not see the definition of the template from here (just the declaration)
  32. // So it relies on the explicit instantiation to make sure it links.
  33. int main()
  34. {
  35. StringAdapter<const char> x("hi There");
  36. x.doAdapterStuff();
  37. }

 

stackoverflow 中愚蠢的例子Silly example:

  1. // StringAdapter.h
  2. template<typename T>
  3. class StringAdapter
  4. {
  5. public:
  6. StringAdapter(T* data);
  7. void doAdapterStuff();
  8. private:
  9. std::basic_string<T> m_data;
  10. };
  11. typedef StringAdapter<char> StrAdapter;
  12. typedef StringAdapter<wchar_t> WStrAdapter;

source:

  1. // StringAdapter.cpp
  2. #include "StringAdapter.h"
  3. template<typename T>
  4. StringAdapter<T>::StringAdapter(T* data)
  5. :m_data(data)
  6. {}
  7. template<typename T>
  8. void StringAdapter<T>::doAdapterStuff()
  9. {
  10. /* Manipulate a string */
  11. }
  12. // Explicitly instantiate only the classes you want to be defined.
  13. // In this case I only want the template to work with characters but
  14. // I want to support both char and wchar_t with the same code.
  15. template class StringAdapter<char>;
  16. template class StringAdapter<wchar_t>;

Main:

  1. #include "StringAdapter.h"
  2. // Note: Main can not see the definition of the template from here (just the declaration)
  3. // So it relies on the explicit instantiation to make sure it links.
  4. int main()
  5. {
  6. StrAdapter x("hi There");
  7. x.doAdapterStuff();
  8. }

 

一些有趣的小知识

《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

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/576801
推荐阅读
相关标签
  

闽ICP备14008679号