类声明和定义_声明和定义

其他范文 时间:2020-02-28 04:51:45 收藏本文下载本文
【www.daodoc.com - 其他范文】

类声明和定义由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“声明和定义”。

如何组织编写模板程序 前言

常遇到询问使用模板到底是否容易的问题,我的回答是:“模板的使用是容易的,但组织编写却不容易”。看看我们几乎每天都能遇到的模板类吧,如STL, ATL, WTL, 以及Boost的模板类,都能体会到这样的滋味:接口简单,操作复杂。

我在5年前开始使用模板,那时我看到了MFC的容器类。直到去年我还没有必要自己编写模板类。可是在我需要自己编写模板类时,我首先遇到的事实却是“传统”编程方法(在*.h文件声明,在*.cpp文件中定义)不能用于模板。于是我花费一些时间来了解问题所在及其解决方法。

本文对象是那些熟悉模板但还没有很多编写模板经验的程序员。本文只涉及模板类,未涉及模板函数。但论述的原则对于二者是一样的。

问题的产生

通过下例来说明问题。例如在array.h文件中有模板类array: // array.h template cla array { T data_[SIZE];array(const array& other);const array& operator =(const array& other);public: array(){};T& operator[](int i){return data_[i];} const T& get_elem(int i)const {return data_[i];} void set_elem(int i, const T& value){data_[i] = value;} operator T*(){return data_;} };

然后在main.cpp文件中的主函数中使用上述模板: // main.cpp #include “array.h” int main(void){ array intArray;intArray.set_elem(0, 2);int firstElem = intArray.get_elem(0);int* begin = intArray;} 这时编译和运行都是正常的。程序先创建一个含有50个整数的数组,然后设置数组的第一个元素值为2,再读取第一个元素值,最后将指针指向数组起点。但如果用传统编程方式来编写会发生什么事呢?我们来看看:

将array.h文件分裂成为array.h和array.cpp二个文件(main.cpp保持不变)// array.h template cla array { T data_[SIZE];array(const array& other);const array& operator =(const array& other);public: array(){};T& operator[](int i);const T& get_elem(int i)const;void set_elem(int i, const T& value);operator T*();};

// array.cpp #include “array.h” template T& array::operator [](int i){ return data_[i];} template const T& array::get_elem(int i)const { return data_[i];} template void array::set_elem(int i, const T& value){ data_[i] = value;} template array::operator T*(){ return data_;}

编译时会出现3个错误。问题出来了: 为什么错误都出现在第一个地方? 为什么只有3个链接出错?array.cpp中有4个成员函数。

要回答上面的问题,就要深入了解模板的实例化过程。模板实例化

程序员在使用模板类时最常犯的错误是将模板类视为某种数据类型。所谓类型参量化(parameterized types)这样的术语导致了这种误解。模板当然不是数据类型,模板就是模板,恰如其名:

编译器使用模板,通过更换模板参数来创建数据类型。这个过程就是模板实例化(Instantiation)。

从模板类创建得到的类型称之为特例(specialization)。

模板实例化取决于编译器能够找到可用代码来创建特例(称之为实例化要素,point of instantiation)。

要创建特例,编译器不但要看到模板的声明,还要看到模板的定义。模板实例化过程是迟钝的,即只能用函数的定义来实现实例化。

再回头看上面的例子,可以知道array是一个模板,array是一个模板实例-一个类型。从array创建array的过程就是实例化过程。实例化要素体现在main.cpp文件中。如果按照传统方式,编译器在array.h文件中看到了模板的声明,但没有模板的定义,这样编译器就不能创建类型array。但这时并不出错,因为编译器认为模板定义在其它文件中,就把问题留给链接程序处理。

现在,编译array.cpp时会发生什么问题呢?编译器可以解析模板定义并检查语法,但不能生成成员函数的代码。它无法生成代码,因为要生成代码,需要知道模板参数,即需要一个类型,而不是模板本身。

这样,链接程序在main.cpp 或 array.cpp中都找不到array的定义,于是报出无定义成员的错误。

至此,我们回答了第一个问题。但还有第二个问题,在array.cpp中有4个成员函数,链接器为什么只报了3个错误?回答是:实例化的惰性导致这种现象。在main.cpp中还没有用上operator[],编译器还没有实例化它的定义。解决方法

认识了问题,就能够解决问题:

在实例化要素中让编译器看到模板定义。

用另外的文件来显式地实例化类型,这样链接器就能看到该类型。使用export关键字。

前二种方法通常称为包含模式,第三种方法则称为分离模式。

第一种方法意味着在使用模板的转换文件中不但要包含模板声明文件,还要包含模板定义文件。在上例中,就是第一个示例,在array.h中用行内函数定义了所有的成员函数。或者在main.cpp文件中也包含进array.cpp文件。这样编译器就能看到模板的声明和定义,并由此生成array实例。这样做的缺点是编译文件会变得很大,显然要降低编译和链接速度。

第二种方法,通过显式的模板实例化得到类型。最好将所有的显式实例化过程安放在另外的文件中。在本例中,可以创建一个新文件templateinstantiations.cpp: // templateinstantiations.cpp #include “array.cpp” template cla array;// 显式实例化

array类型不是在main.cpp中产生,而是在templateinstantiations.cpp中产生。这样链接器就能够找到它的定义。用这种方法,不会产生巨大的头文件,加快编译速度。而且头文件本身也显得更加“干净”和更具有可读性。但这个方法不能得到惰性实例化的好处,即它将显式地生成所有的成员函数。另外还要维护templateinstantiations.cpp文件。第三种方法是在模板定义中使用export关键字,剩下的事就让编译器去自行处理了。当我在Stroustrup的书中读到export时,感到非常兴奋。但很快就发现VC 6.0不支持它,后来又发现根本没有编译器能够支持这个关键字(第一个支持它的编译器要在2002年底才问世)。自那以后,我阅读了不少关于export的文章,了解到它几乎不能解决用包含模式能够解决的问题。欲知更多的export关键字,建议读读Herb Sutter撰写的文章。

结论

要开发模板库,就要知道模板类不是所谓的“原始类型”,要用其它的编程思路。本文目的不是要吓唬那些想进行模板编程的程序员。恰恰相反,是要提醒他们避免犯下开始模板编程时都会出现的错误。

////////////////////////////// http://www.daodoc.com,.cxx)扩展名。

这种组织方式工作的很好:它使得在编程时可以方便地访问所需的类型定义,并且避免了来自链接器的“变量或函数重复定义”的错误。

由于以上组织方式约定的影响,模板编程新手往往会犯一个同样的错误。下面这一小段程序反映了这种错误。就像对待“普通代码”那样,我们在头文件中定义模板: // basics/myfirst.hpp #ifndef MYFIRST_HPP #define MYFIRST_HPP // declaration of template template void print_typeof(T const&);#endif // MYFIRST_HPP print_typeof()声明了一个简单的辅助函数用来打印一些类型信息。函数的定义放在点C文件中:

// basics/myfirst.cpp #include #include #include “myfirst.hpp” // implementation/definition of template template void print_typeof(T const& x){ std::cout

大部分C++编译器(Compiler)很可能会接受这个程序,没有任何问题,但是链接器(Linker)大概会报告一个错误,指出缺少函数print_typeof()的定义。

这个错误的原因在于,模板函数print_typeof()的定义还没有被具现化(instantiate)。为了具现化一个模板,编译器必须知道哪一个定义应该被具现化,以及使用什么样的模板参数来具现化。不幸的是,在前面的例子中,这两组信息存在于分开编译的不同文件中。因此,当我们的编译器看到对print_typeof()的调用,但是没有看到此函数为double类型具现化的定义时,它只是假设这样的定义在别处提供,并且创建一个那个定义的引用(链接器使用此引用解析)。另一方面,当编译器处理myfirst.cpp时,该文件并没有任何指示表明它必须为它所包含的特殊参数具现化模板定义。头文件中的模板

解决上面这个问题的通用解法是,采用与我们使用宏或者内联函数相同的方法:我们将模板的定义包含进声明模板的头文件中。对于我们的例子,我们可以通过将#include “myfirst.cpp”添加到myfirst.hpp文件尾部,或者在每一个使用我们的模板的点C文件中包含myfirst.cpp文件,来达到目的。当然,还有第三种方法,就是删掉myfirst.cpp文件,并重写myfirst.hpp文件,使它包含所有的模板声明与定义:

// basics/myfirst2.hpp #ifndef MYFIRST_HPP #define MYFIRST_HPP #include #include // declaration of template template void print_typeof(T const&);// implementation/definition of template template void print_typeof(T const& x){ std::cout

从这个方法中我们可以得到一些观察结果。最值得注意的一点是,这个方法在相当程度上增加了包含myfirst.hpp的开销。在这个例子中,这种开销并不是由模板定义自身的尺寸引起的,而是由这样一个事实引起的,即我们必须包含我们的模板用到的头文件,在这个例子中是和。你会发现这最终导致了成千上万行的代码,因为诸如这样的头文件也包含了和我们类似的模板定义。

这在实践中确实是一个问题,因为它增加了编译器在编译一个实际程序时所需的时间。我们因此会在以后的章节中验证其他一些可能的方法来解决这个问题。但无论如何,现实世界中的程序花一小时来编译链接已经是快的了(我们曾经遇到过花费数天时间来从源码编译的程序)。

抛开编译时间不谈,我们强烈建议如果可能尽量按照包含模式组织模板代码。

另一个观察结果是,非内联模板函数与内联函数和宏的最重要的不同在于:它并不会在调用端展开。相反,当模板函数被具现化时,会产生此函数的一个新的拷贝。由于这是一个自动的过程,编译器也许会在不同的文件中产生两个相同的拷贝,从而引起链接器报告一个错误。理论上,我们并不关心这一点:这是编译器设计者应当关心的事情。实际上,大多数时候一切都运转正常,我们根本就不用处理这种状况。然而,对于那些需要创建自己的库的大型项目,这个问题偶尔会显现出来。

最后,需要指出的是,在我们的例子中,应用于普通模板函数的方法同样适用于模板类的成员函数和静态数据成员,以及模板成员函数。

声明类广告

声明公告刊登格式(1)身 份 证:××二代(一代)身份证(证号)于0000年0月0日丢失,特此声明。(2)驾驶证: 李××驾驶证(证号)于0000年0月0日丢失,特此声明......

VC类定义

VC++怎样定义类对象如果你定义了一个类(假设是A)那么声明一个A的对象的方法就是:A a;// a 就是一个A的对象A *a;// a 就是一个A的对象的指针A a[N];// a 就是一个A的对象的数组......

定义银行类

在定义银行类时,若取钱数大于余额则作为异常处理(InsufficientFundsException).思路:产生异常的条件是余额少于取额, 因此是否抛出异常要判断条件取钱是withdrawal([wið'dr......

声明类启事范文

声明类启事范文(精选14篇)由网友“小怪兽”投稿提供,小编在这里给大家带来声明类启事范文,希望大家喜欢!篇1:声明 xxx市xxx镇xxx养蜂场不慎遗失xxx市工商行政管理局于xxx年3月27......

二叉树的类定义

实验一、二叉树的类定义程序说明1、改程序用二叉链存储结构将其生成一棵二叉树;2、分别用三种遍历算法将二叉树的遍历序列输出;3、用括号表示法输出二叉树。二叉树的形状A 程......

下载类声明和定义word格式文档
下载类声明和定义.doc
将本文档下载到自己电脑,方便修改和收藏。
点此处下载文档

文档为doc格式

热门文章
点击下载本文