Effective C++(条款48-50)

条款48:认识template元编程

Template metaprogramming(TMP,模板元编程)是编写template_based C++程序并执行于编译期的过程。所谓的TMP,就是以C++写成,执行于C++编译器内的程序。一旦TMP程序结束执行,其输出,也就是从template具现出来的若干C++源码,便会一如往常地被编译。


TMP可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。而它对于“难以或甚至不可能在运行期实现出来的行为”的表现能力也很吸引人。

条款47中的traits解法就是TMP,其针对不同类型而进行的代码,被拆分为不同的函数,每个函数所使用的操作(操作符)都可施行于该函数所对付的类型。


下面示范TMP循环的例子,因为TMP循环并不涉及递归函数的调用,而是涉及“递归模板具现化”。这个示范TMP的用途就像“hello world”示范任何传统语言的用途一样。

我们通过TMP的阶乘运算示范如何通过“递归模板具现化”实现循环,以及如何在TMP中创建和使用变量:

template<unsigned n>
struct Factorial{
    enum{value=n*Factorial<n-1>::value};
};
template<>
struct Factorial<0>{
    enum{value=1};
};
//你可以这样使用Factorial
int main()
{
    std::cout<<Factorial<5>::value;//印出120
    std::cout<<Factorial<10>::value;//印出3628800
}


为求领悟TMP之所以值得学习,很重要的一点是先对它能够达成什么目标有一个较好的理解:

  • 确保量度单位正确。使用TMP可以确保在编译期程序中所有量度单位的组合都正确,不论计算多么复杂,这也是为什么TMP可以被用来进行早期错误侦测。
  • 可以生成客户定制的设计模式实现品。TMP可被用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成某些特殊类型并不适合的代码。
  • 优化矩阵运算。考虑以下代码:                                                                                                                                                                                                                                                
    typedef SquareMatrix<double,10000> BigMatrix;
    BigMatrix m1,m2,m3,m4,m5;
    ...
    BigMatrix result=m1*m2*m3*m4*m5;

              以“正常的函数”调用动作来计算result,会创建4个暂时性矩阵,每一个用来存储operator*的调用结果。如果使用与TMP相关的template技术,就有可能消除哪些临时对象并合并循环,这一切都无需改变客户端的用法(像上面那样)




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

了解C++内存管理例程的行为,两个主角是分配例程(operator new)和归还例程(operator delete),配角是new-handler,这是当operator new 无法满足客户的内存需求时所调用的函数。

operator new 和operator delete的一些事情也都适用于operator new[]和operator delete[]。

请注意:STL容器所使用的heap内存是由容器所拥有的分配器对象管理,不是被new 和delete直接管理



当operator new无法满足某一内存分配需求时,它会抛出异常,当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler。set_new_handler允许客户指定一个函数,在内存分配无法满足的时候被调用

namespace std{
    typedef void (*new_handler)();//new_handler是个typedef,定义出一个指针指向函数,该函数没有参数也不返回任何东西
    new_handler set_new_handler(new_handler p) throw();//表示该函数不抛出任何异常
                                                       //返回的是在set_new_handler()被调用前正在执行(但马上要被替换)的那个new_handler函数的指针

你可以这样使用set_new_handler

void outOfMem()
{
    std::cerr<<"unable to satisfy request for memory\n";
    std::abort();
}
int main()
{
    std::set_new_handler(outOfMem);
    int* pBigDataArray=new int[100000000L];
    ...
}

当operator new无法满足内存申请时,它会不断的调用new-handler函数,直到找到足够内存


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

  • 让更多的内存可被使用。这便造成operator new内的下一次内存分配动作可能成功。实现此策略的一个做法是:程序一开始执行就分配一大块内存,而后当new-handler第一次被调用,将他们 释还给程序使用
  • 安装另一个new-handler。使用set_new_handler调用其他版本的new-handler函数,这个新版本修改自己的行为以做些不同的事情。常见的做法之一是令new-handler修改“会影响new-handler行为”的static数据、namespace数据或global数据。
  • 卸除new-handler。也就是将null指针传给set_new_handler。一旦没有安装任何new-handler,operator new会在内存分配不成功时抛出异常。
  • 抛出bad_alloc(或派生自它)的异常。这样的异常不会被operator new捕捉,因此会被传播到内存索求处。
  • 不返回。通常调用abort或exit。


有时候你或许会希望以不同的方式处理内存分配失败的情况,你希望视被分配物属于哪个class而定,一个做法就是建立一个base class,这种base class用来允许derived class继承单一特定能力,在本例中是“设定class专属的new-handler能力”,然后将这个base class 转换为template,这个设计的base class 部分让继承类继承他们所需的set_new_handler和operator new,而template部分则确保每一个derived class获得一个实体互异的currentHandler成员变量。它现在可以被任何有需要的class使用,甚至这个技术还拥有一个自己的名称“怪异的循环模板模式”(CRTR,curiously recurring template pattern)。
template<typename T>
class NewHandlerSupport{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();//static成员必须在class定义式之外被定义,除非他们是const而且是整数型,见条款2
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    ...
private:
    static std::new_handler currentHandler;
};
template<typename T>
std::new_handler NewHandlerSupport<T>::set_handler(std::new_handler p)throw()
{
    std::new_handler oldHandler=currentHandler;
    currentHandler=p;
    return oldHandler;
}
template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size)
throw(std::bad_alloc)
{
    NewHandleSupport h(std::set_new_handler(currrentHandler));//安装T的new_handler。
    return::operator new(size);//分配内存或抛出异常。恢复global new_handler。
}

template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler=0;//将每一个currentHandler初始化为null
//有了这个class template,为一个名为Widget的类添加set_new_handler支持能力就很简单了,如下:
class Widget:public NewHandlerSupport<Widget>{
    ...
};//不必声明set_new_handler和operator new

类型参数T只是用来区分不同的derived class。template机制会自动为每一个T(NewHandlerSupportL赖以具现化的依据)生成一份currentHandler。

另外有一个形式的operator new,负责供应传统的“分配失败便返回null”行为,这个形式被称为 nothrow形式,某种程度上是因为他们在new的使用场合中用来nothrow对象(定义于头文件<new>):
class Widget{...}
Widget* pw1=new Widget;
if(pw1==0)...//这个测试一定失败
Widget* pw2=new(std::nothrow)Widget;//如果分配失败,返回0
if(pw2==0)...//这个测试可能成功

虽然new(std::nothrow) Widget调用operator new失败后返回null并不抛掷异常,但Widget构造函数却有可能抛出异常,该异常会一如往常地传播。 所以使用nothrow new 只能保证operator new不抛掷异常,不能保证像new(std::nothrow) Widget的表达式不抛掷异常,nothrow是一个颇为具现的工具,其实你没有运用它的需要。nothrow new对异常的强制性保证性并不高






条款50:了解new和delete的合理替换时机
首先,怎么会有人想要替换编译器提供的operator new和operator delete呢?下面是三个最常见的理由:
  • 用来检测运用上的错误。如果将new所得内存delete掉却不幸失败,或导致内存泄露。如果在new所得内存身上多次delete则会导致不明确的行为。此外各式各样的编程错误可能会导致数据overruns(写入点在分配区尾端之后)或underruns(写入点在分配区起点之前)。如果我们自定义一个operator new,便可以超额分配内存,以额外空间(位于分配区之前或之后)放置特定的签名。使用operator delete得以检查上述签名是否原封不动。
  • 为了强化效能。因为现实存在许多不同的堆内存管理器的要求,因此编译器所带的new 和delete采用中庸之道,他们的工作对每个人都是适当地好,但不对特定任何人有最佳表现。定制版的new 和delete性能胜过缺省版本。
  • 为了收集 使用上的统计数据。自行定义的new和delete使我们得以轻松收集到很多信息,比如分配区块的大小分布、寿命分布、是以先进先出还是后进先出次序互随机次序来分配和归还、任何时刻所使用的最大动态分配量是多少等等。
  • 为了增加分配和归还的速度。泛用型的分配器往往(并不总是)比定制型的分配器慢,特别是当定制型分配器专门针对某特定类型的对象而设计时。
  • 为了降低缺省内存管理器带来的空间额外开销。泛用型内存管理器往往比定制型使用更多的内存。
  • 为了弥补缺省分配器中的非最佳齐位。在X86体系结构中double的访问速度最快速——如果他们都是8-byte齐位。但是编译器自带的operator new并不保证对动态分配而得的double采取8-byte齐位。
  • 为了将相关对象成簇集中。如果你知道特定某个数据结构往往被一起使用,而你希望在处理这些数据时将“内存页错误”的频率降至最低,那么为此数据创建另一个heap就有意义。
  • 为了获得非传统的行为。有时候你会希望operator new和operator delete做编译器附带版没做的事情。例如你可能会希望分配和归还内存内的区块,但唯一能够管理该内存的只有C API函数,那么写下一个定制版的new 和delete,你便为C API穿上C++外套,你也可以写自定的operator delete,在其中将所有归还内存内容覆盖为0,增加应用程序的数据安全性。
本条款的主题是,了解何时可在“全局性的”或“class专属的”基础上合理替换缺省的new和delete

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值