前天晚上看了下数据结构的C++描述版,然后就动手写了个链表的类,用到了类模板,我是这样定义的,类ChainNode和Chain分别表示链表 的节点和链表本身,两个类的声明放在名为chain.h的头文件中,而又加了一个名为chain.cpp的源文件来实现这两个类中的方法。因为是最基本的 链表操作,所以很快就写完了,然后在main函数里面加了几天测试代码,编译链接。这时候问题来了,两个类里面的方法都链接报错,说是找不到实现,这让我 很奇怪,因为我明明在chain.cpp里面都有实现,为什么找不到,一开始我考虑到是否是因为在我实现的时候语法不对,因为我知道使用模板类和普通类不 同,有很多要注意的地方,例如实现方法Search时,不能写成:
bool Chain::Search(int i, T& x) {}
而是要写成:
template
bool Chain
于 是把C++ primer又翻出来,看了类模板,确保实现的时候语法是正确的,但是看来看去也没有发现什么问题,编译的时候总是报错找不到实现。于是我修改了 chain.cpp,故意在里面加入了明显错误的代码,但是即使这样,编译chain.cpp仍然不报错,于是我怀疑难道是chain.cpp没有被加入 到工程以至于没有被编译?又查了半天,排除了这个可能,然后我就郁闷了,到底是怎么回事呢,没道理啊。后来Google了一把,总算找到了原因,那篇文章 是这样解释的:
因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和 实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接 程序找地址。但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。
在C++编程思想里的第15章,模板与容器类,有这么一段解释:
甚至是在定义非内联函数时,模板的头文件中也会放置所有的声明和定义。这似乎违背了通常的头文件规则:“不要在分配存储空间前放置任何东西”,这条规则是为 了防止在连接时的多重定义错误。但模板定义很特殊。由template<>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处 于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板 声明和定义。
有时,也可能为了满足特殊的需要(例如,强制模板实例仅存在于一简单的 Windows DLL文件中)而要在一个独立的C P P文件中放置模板的定义。大多数编译器有一些机制允许这么做,那么我们就必须检查我们特定的编译器说明文档以便使用它。
看到这些解释,我就明白了,把Chain的定义放在chain.cpp里面是没用的,确实会导致链接的时候找不到定义。
今 天上午看C++编程思想,在第三章看到一段话,让我想起以前遇到的一个问题,有一次,碰到了这种情况,有两个类:ClassA和 ClassB,ClassA中有一个成员变量是ClassB,同样,ClassB中也有个成员变量是ClassB。ClassA和ClassB在同一头文 件中声明,这时问题就来了,如果先声明ClassA,那么就会报错,说ClassB找不到,而如果先声明ClassB,那么也会报错,说ClassA找不 到,情况如下:
class ClassA
{
public:
ClassA();
~ClassA();
private:
ClassB m_classb;
int m_A;
};
class ClassB
{
public:
ClassB();
~ClassB();
private:
ClassA m_classa;
int m_B;
};
这 种情况下,如何解决问题?在第三章的某处,先是说明了这么做出错的原因:因为如果试图传递整个对象,那么编译器就必须知道这个对象的全部完全的定义以确定 它的大小以及如何传递它,如果编译器找不到给对象的完整定义,那么就会失败。上面正是这种情况,因为如论是先声明ClassA或者ClassB或者两者两 者都声明,但是因为都没用完整的定义,所以编译器就报错了。那么知道了问题的原因,如何解决?可以稍微修改一下,把成员变量改成指针:ClassB* m_pClassb;和ClassA* m_pClassa; 然后在前面加上声明class ClassA;class ClassB;这样就可以了。为什么?因为编译器知道如何传递一个地址,地址的大小是固定的,而不用管地址里的保存的对象类型如何,它的大小有多大。
当然从设计的角度来讲,这ClassA和ClassB这种设计肯定是很不好的,因为耦合度太高了。
没有评论:
发表评论