1.“之所以引进函数模板,一是因为我们已经很清楚,需要有模板类的成员函数;二是因为如果没有这种东西,模板的概念看起来就不够完全。
//declaration of a template function: template<class T> void sort(vector<T>&); void f(vector<int>& vi, vector<String>& vs) { sort(vi); //sort(vector<int>& v); sort(vs); //sort(vector<String>& v); } //definition of a template function: template<class T> void sort(vector<T>& v) /* Sort the elements into increasing order Algorithm: bubble sort (inefficient and obvious) */ { unsigned int n=v.size(); for(int i=0; i<n-1; i++) for(int j=n-1; i<j; j--) if(v[j] < v[j-1]) {// swap v[j] and v[j-1] T temp = v[j]; v[j] = v[j-1]; v[j-1] = temp; } }”(p.307~308) 2.“使用尖括号<…>而不用圆括号,是因为用户发现这样写法更容易读,也因为圆括号在C和C++中已经使用过度。”(p.314) 3.“一般说,从模板类出发的派生提供了一种可能性,使人可以剪裁基类的信息以适应派生类的需要。这提供了一些特别有力的组合模式,例如:
template<class T> class Base {/*...*/}; class Derived : public Base<Derived> {/*...*/};通过这种技术,我们可以将派生类的信息嵌入基类之中。 ”(p.315~316)
1.“在连接程序运行时,如果缺少了某些模板函数实例,它就会调用编译程序,从模板的源代码生成所缺的目标代码。这个过程反复进行,直到所有模板都完成了实例化。”(p.322~323) 2.“实例化请求具有如下形式:
template class vector<int>; //class template int& vector<int>::operator[](int); //member template int convert<int, double>(double); //function我们将关键字template又一次用到了这里,因为不希望引进新关键字instantiate。模板声明与实例化请求是很容易区分的,模板定义的开始总是列出模板参数表template<,仅有template就表示是实例化请求。 ”(p.324)
1.“按照传统,C++程序和C程序一样都是由一组文件构成的。这些文件被组合进一个个编译单元,许多文件依据规定被编译后连接到程序中。例如,.c文件是源文件,它们通过包含.h文件以获得程序其他部分的信息。编译程序从一些.c出发去生成目标文件,常常称为.o文件。程序的可执行版本就是简单地通过连接这些.o而得到的。档案(archive)和动态连接库使问题进一步复杂化了,但是并没有改变这个整体画面。 模板并不能很好地放进这个画面中,这也正是与模板实现有关的许多问题的根源。从一方面看,一个模板不仅仅是一段源代码(通过模板实例化产生的更像传统意义上的源代码),一些模板定义也不像是属于.c文件;从另一方面看,模板并不正好就是类型和界面信息,因此它们也不像是属于.h文件。”(p.331~332)
1.“模板机制完全是编译时和连接时的机制,模板机制的任何部分都不需要运行时支持。这当然是经过深思熟虑的,但也遗留下一个问题:如何让从模板产生的(实例化出来的)类和函数能够依靠那些只有到了运行时才能知道的信息?与C++的其他地方一样,回答是使用虚函数。”(p.334)
1.“一个模板参数可以是内部类型的,也可以是用户定义类型的。这就产生了一种持续的压力,要求用户定义类型无论是在外观上,还是在行为上都要尽可能与内部类型相仿。可惜,用户定义类型和内部类型不可能具有完全一样的行为,因为无法做到清除C语言内部类型的不规范性,而又不严重地影响与C语言的兼容性。在许多相对较次要的方面,内部类型也从模板带来的进步中有所获益。”(p.335 )
1.“要想提供一些功能使一个单独的程序就能从所有错误中恢复,这完全是一种误导,也会使错误处理策略变得非常复杂,其本身还会成为新的错误根源。”(p.339)
1.“
int f() { try{ //start of try block return g(); } catch(xxii){ //start of exception handler //we get here only if 'xxii' occurs error("g() goofed:xxii"); return 22; } } int g() { try{ //start of try block return g(); } catch(xxii){ //start of exception handler //we get here only if 'xxii' occurs error("g() goofed: xxii"); return 22; } } int g() { //... if(something_wrong) throw xxii(); //throw exception //... }这里的try关键字完全是多余的,那些花括号{}也是一样的,除了真正需要在try块中或异常处理器中使用多个语句的情况之外…我曾经试者用catch表示抛出和捕捉两种操作,这完全可以做得符合逻辑,也具有内在的一致性,但我在给人们解释这种模式时却没有成功。 选择关键字throw,部分原因是比它更鲜明的词,例如raise和signal,都已经被C语言的标准库函数使用了。”(p.340)
1.“资源应该以它们被分配的相反顺序进行释放,在典型情况下,这一点是非常重要的。这与局部对象通过构造函数创建,而后由析构函数销毁的行为方式极其相似。因此,我们可以将资源的请求和释放问题用一个带有构造函数和析构函数的类的对象来适当处理。”(p.342)
1.“在异常处理机制的设计期间,引起最大争议的问题是它究竟应该支持哪种语义,是终止语义还是唤醒语义。也就是说,异常处理器是否应该能提出请求,要求从异常的抛出点重新唤醒程序的执行…在后来的4年里我学到了许多其他东西,因此,C++的异常处理机制采纳了相反的观点,通常被称为‘终止模型’。”(p.344) 2.“终止比唤醒更好,这不是一种观点的问题,而是许多年的经验。唤醒是非常诱人的,却是站不住脚的。”(p.345)
