effective C++ 读书笔记(下)

来源:互联网 发布:网络推手是什么意思 编辑:程序博客网 时间:2024/05/02 01:47

条款42:了解typename的双重意义

      C++里在模板定义中“typename”和“class”可以互换。但是有时候,必须使用typename:

 

      上面的代码是一个简单的函数模板,初看起来似乎没有什么问题,但是C::const_iterator却不一定是一种类型(默认情况下,它在迭代器里确实是一种类型,但具体到C,即使C是一种容器,C里面的局部变量定义还是可能覆盖这个类型定义),因此,此时必须在C::const_iterator前加上typename。

      还有一个地方要注意的是代码里的两个局部变量iter和value。iter的类型是由模板参数决定的,这称为从属名称,对应的value称为非从属名称。如果从属名称在类里面呈嵌套状,则称为嵌套从属名称(即在类里面定义的类)。

      typename不可以出现在派生类声明里的基类列表里的嵌套从属类型名称之前,也不可以在成员初始化列表里出现作为基类修饰符。这句话听着可能有点不好理解,看看下面的例子就了然了:

 

 

条款44:将与参数无关的代码抽离templates

      templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数。因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。

 

条款45:运用成员函数模板接受所有兼容类型

      同一个template的不同具现体之间并不存在什么与生俱来的固有关系(意指如果以带有base-derived关系的B,D两类型分别具现化某个template,产生出来的两个具现体并不带有base-derived关系)。所以如果B为D的基类,而template<B>和template<D>分别是两个模板的具现体,那么template<B>和template<D>并没有继承关系。

      看看下面的代码:

 

      上面代码的意思是,对任何类型T和任何类型U,这里可以根据SmartPtr<U>生成一个SmartPtr<T>,因为SmartPtr<T>有个构造函数接受一个SmartPtr<U>参数。这一类构造函数根据对象u创建对象t,而u和t的类型是同一个template的不同具现体,有时我们称之为泛化 copy构造函数。

      完成声明之后,这个为SmartPtr而写的“泛化copy构造函数”提供的东西比我们需要的更多。是的,我们希望根据一个SmartPtr<Bottom>创建一个SmartPtr<Top>,却不希望反过来。(书中Top是Bottom的基类。)假设SmartPtr遵循auto_ptr和tr1::shared_ptr所提供的榜样,也提供一个get成员函数,返回智能指针对象所持有的那个原始指针的副本,那么我们可以在“构造模板”实现代码中约束转换行为,使它符合我们的期望:

 

      此处使用成员初值列来初始化SmartPtr<T>之内类型为T*的成员变量,并以类型为U*的指针作为初值。这个行为只有当“存在某个隐式转换可将一个U*指针转为一个T*指针”时才能通过编译,而那正是我们想要的。最终效益是SmartPtr<T>现在有了一个泛化copy构造函数,这个构造函数只在其所获得的实参隶属适当(兼容)类型时才它通过编译。

 

条款46:需要类型转换时请为模板定义非成员函数

      在template实参推导过程中从不将隐式类型转换函数考虑在内。

 

条款47:请使用traits classes表现类型信息

      这里提到了traits,关于它的功能和使用,可参看我转载的一篇博客。http://blog.csdn.net/nkorange/archive/2011/03/21/6265159.aspx

 

条款49:了解new-handle的行为

      当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handle。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handle,那是声明于<new>的一个标准程序库函数:

 

      new_handler是个typedef,定义出一个指针指向函数,该函数没有参数也不返回任何东西。set_new_handler则是“获得一个new_handler并返回一个new_handler”的函数。set_new_handler声明式尾端的“throw()”是一份异常明细,表示该函数不抛出任何异常。

      set_new_handler的参数是个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是个指针,指向set_new_handler被调用前正在执行的那个new_handler函数。可以如下使用:

 

      如果operator new无法为pBigDataArray分配足够空间,outOfMem会被调用。

      设计一个良好的new-handler函数必须做以下事情:

  • 让更多内存可被使用。 这便造成operator new内的下一次内存分配动作可能成功。实现此策略的一个做法是,程序一开始就分配一大块内存,而后当new-handler第一次被调用,将它们释还给程序使用。
  • 安装另一个new-handler。 如果目前这个new-handler无法取得更多可用内存,或许它知道另外哪个new-handler有此能力。果真如此,目前这个new-handler就可以安装另外那个new-handler以替换自己(只要调用set_new_handler)。下次当operator new调用new-handler,调用的将是新安装的那个。(在new-handler里改变自己)。
  • 卸载new-handler, 也就是将null指针传给set_new_handler。一旦没有安装任何new-handler,operator new会在内存分配不成功时抛出异常。
  • 抛出bad_alloc。这样的异常不会被operator new捕捉,因此会被传播到内存索求处。
  • 不返回, 通常调用abort或exit。

      如果要为类定制new-handler,只需令每一个class提供自己的set_new_handler和operator new即可。

 

      Widget的operator new主要做的事情是:调用set_new_handler,告知Widget的错误处理函数。这会将Widget的new-handler安装为global new-handler。然后调用global new-handler执行实际内存分配。在此之后(无论内存分配成功与否),旧的new-handler必须被恢复(如何做:存储起原来的new-handler,在适当时机恢复,如析构或new-handler。)。下面是书上的代码:

 

条款50:了解new和delete的合理替换时机

此处讨论了齐位问题,不要让指针随便加上偏移量(比如double型指针被转换为char型,然后偏移若干字节)。

 

条款51:编写new和delete时需固守常规

operator new的返回值十分单纯。如果它有能力供应客户申请的内存,就返回一个指针指向那块内存。如果没有那个能力,则抛出一个异常。operator new尝试不止一次分配内存。

 

条款52:写了placement new也要写placement delete

placement new,placement delete。