c++的模板问题
高洛峰
高洛峰 2017-04-17 13:04:54
0
5
449

ArrayList.h

#pragma once
template <class T>
class ArrayList
{
public:
    ArrayList(int size);
    ~ArrayList(void);

private:
    T* arrayList;
    int maxSize;
};

ArrayList.cpp

#include "ArrayList.h"

template <class T>
 ArrayList<T>::ArrayList(const int size)
{
    maxSize = size;
    arrayList = new T[maxSize];
}

 template <class T>
 ArrayList<T>::~ArrayList(void)
{
    delete [] arrayList;
}

源.cpp

#include <iostream>
#include "ArrayList.h"
using namespace std;

int main()
{
    ArrayList<int> list(2);
    system("pause");
    return 0;
}

出现的错误是

将三个文件写到一个文件里后就没有错误了

#include <iostream>
using namespace std;

template <class T>
class ArrayList
{
public:
    ArrayList(int size);
    ~ArrayList(void);

private:
    T* arrayList;
    int maxSize;
};

template <class T>
 ArrayList<T>::ArrayList(int size)
{
    maxSize = size;
    arrayList = new T[maxSize];
}

 template <class T>
 ArrayList<T>::~ArrayList(void)
{
    delete [] arrayList;
}

 int main()
{
    ArrayList<int> list(2);
    system("pause");
    return 0;
}

请问这是为什么

高洛峰
高洛峰

拥有18年软件开发和IT教学经验。曾任多家上市公司技术总监、架构师、项目经理、高级软件工程师等职务。 网络人气名人讲师,...

全部回复(5)
刘奇

简单答案是“模板要写在.h文件里”。
但我想这不是题主想要的。

要搞清楚这问题,你得先明白分开.h和.cpp的意义。

平常我们分开.cpp和.h,为的并不是模块化。
模块化本身体现在“把不同的东西分别放在不同的文件”,但.cpp和.h却是“把相同的东西分别放在两个文件”。
事实上,你大可以完全不写.h档,只写.cpp,然后#include "bla.cpp",一样可以通过编译。
(只要.cpp里的函数/变量/类等的名字没撞车……)

分开.h和.cpp的最大原因,在于把各.cpp分开编译。

在建构程序时,我们把所有.cpp(源码文件)编译成.o(目标文件),然后把.o整合成为可执行文件(或者库)。
假设我们的软件项目里,有100个.cpp文件,而在上次建构之后,我们只改了其中一个。
那么最好的情况是只有一个.o文件需要重新编译,其余99个可以直接用来整合可执行文件,省下编译时间。
这对于建构大型软件来说很有用,总不能你改了Office的一行代码,就把数十GB的源码都重新编译吧……

然后我们有另一个问题:
.cpp全部都分开了,互相不知道彼此的存在,那如果a.cpp要用到b.cpp里的东西,怎么办?
a.cpp需要知道b.cpp提供了甚么功能,但不需要知道那些功能是怎么实现的。
而“展示所提供的功能”就是.h文件的作用:我提供了名为BlaBlaBla的类,里面有某某变量某某方法,还有个叫做doSomething的函数,它接受两个int作为参数,……
于是所有.cpp只需要引入.h,看看能做些甚么就行了。
它们不需要管.h里说的那些功能是怎样实现的,反正只要知道最终整合为可执行文件事,这些事都能完成,就够了。
这就实现了依赖分离,从而让各.cpp可以分开编译。

但模板不同:模板没办法分开编译。
为甚么?看看以下的例子:

template <typename T>
T* makeArray(size_t n) {
  return new T[n];
}

编译器看到了这行代码,它应该做甚么?
一般情况下,假如编译器看到一个类似的东西:

SomeClass* makeArray(size_t n) {
  return new SomeClass[n];
}

它应该在.o里写下类似这样的二进制代码:

分配一段足够长的空间(n * sizeof(SomeClass))
找到SomeClass对应的构造函数
对空间里的每一个sizeof(SomeClass)大小的位置,执行一遍SomeClass的构造函数
返回空间的地址

但在模板的例子里,它办不到:它根本不知道T是甚么鬼,sizeof(T)不明,无法编译!

另一个问题在于,模板有些特性,让你不到实际知道T之前,根本不知道编译出来的代码长甚么样子。
其中一个叫做偏特化:

template<typename T>
int getValue(T t) {
    return t.value;
}

template<>
int getValue(int t) {
    return 0;
}

这东西怎么编译?在不知道T是甚么的情况下,你根本不知道该用上面的版本还是下面的版本……

因此,模板代码无法被编译成.o文件。
其他.cpp要使用这段模板,只有直接把它复制贴上到自己的代码里,也就是#include干的事。
事实上,这也体验了“模板”这个名字:我只是个源码的模板而已,是用来方便你复制贴上然后修改的,不是用来编译的!

伊谢尔伦

那个报错是因为模板无法实例化。类模板的声明及其成员函数的定义要放在同一个文件,即放在.h文件中。

黄舟

我来告诉题主你终极的答案。

其实这个模板写cpp里面的事情早就有人propose了。世界上有一个最牛逼的公司叫EDG,负责写正确的C++编译器,然后告诉标准委员会他们的看法,顺便卖一下他们的C++/Java/Fortran的编译器前端。他们花了两年终于把这件事情给做出来了,里面包括如何把模板代码的AST序列化进obj/lib以便于在链接的时候再展开重新编译一边等超级复杂的工作。最后得出一个结论:

这么牛逼的功能,只有我们知道怎么做,你们是做不出来的。

所以全世界就作罢了,再也不提这件事情。

因此其它的答案说了一大堆道理其实都是不正确的,因为并没有什么理由阻止C++编译器在链接的时候才编译模板的源代码,这全部都是工程上的取舍。做这件事情,好处又几乎没有,但是编译器的复杂度又上升了一大截。所以没有什么意义。

伊谢尔伦

如果不用#pragma once 改成用这个试试看呢

#ifndef ARRAY_LIST_H
#define ARRAY_LIST_H


#endif
大家讲道理

实例化模板类及函数时需要“看到”相关实现,不然编译器怎么特例化这部分代码呀?

热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板