编写高质量代码:改善C++程序的150个建议(十一)

建议21:尽量用new/delete代替malloc/free

  在C语言中,我们已经熟悉利用malloc/free来管理动态内存,而在C++中,我们又有了新的工具:new/delete。你不禁会产生疑问—有了malloc/free为什么还要new/delete 呢?使用malloc/free和使用new/delete又有什么区别呢?首先来分析一下下面的代码片段:

  1. class Object  
  2. {  
  3. public:  
  4.      Object()  
  5.      {  
  6.           cout << "Hello, I was born." << endl;  
  7.      }  
  8.      ~Object()  
  9.      {  
  10.       cout << "Bye, I am died." << endl;  
  11. }  
  12.  void Hello()  
  13.  {  
  14.    cout  <<  "I am Object."<<endl;  
  15.  }  
  16. };  
  17. int  main()  
  18. {  
  19.    cout  <<  " Using Malloc & Free... "<<endl;  
  20.    Object* pObjectA = (Object*)malloc(sizeof(Object));  
  21.    pObjectA->Hello();  
  22.    free pObjectA;  
  23.    cout  <<  " Using New & Delete... "<<endl;  
  24.    Object* pObjectB = new Object;  
  25.    pObjectB->Hello();  
  26.    delete pObjectB;  
  27.    return  0;  
  28. }

  代码运行的结果为:

  1. Using Malloc & Free...  
  2. I am Object.  
  3. Using New & Delete...  
  4. Hello, I was born.  
  5. I am Object.  
  6. Bye, I am died.

  通过结果我们可以得知:new/delete在管理内存的同时调用了构造和析构函数;而malloc/free仅仅实现了内存分配与释放。接下来,我们进行详细讨论。

  malloc/free是C/C++语言的标准库函数,而new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

  由于malloc/free是库函数,所以需要对应的头文件库函数支持。对于非内置数据类型的对象,用malloc/free无法满足创建动态对象的要求。这是因为对象在创建的同时要自动执行构造函数,对象在消亡之前则要自动执行析构函数。由于malloc/free不是运算符,不受编译器的控制管辖,所以不能够把执行构造函数和析构函数的任务强加于malloc/free上。而new/delete就不同了,它们是保留字,是操作符,它们和“+”、“-”、“*”、“/”有着一样的地位。new不仅能完成动态内存分配,还能完成初始化工作,稳妥地构造对象;delete不仅能完成内存的释放,还能进行对象的清理。举个形象的例子:通过new建造出来的是一栋房子,可以直接居住;而通过malloc申请到的仅仅是一块地皮,要想成为房子,还需要做出另外的努力。

  malloc的语法是:

指针名=(数据类型*)malloc(长度); //(数据类型*)表示指针

  new的语法是:

  1. 指针名 = new 类型(参数); // 单个对象  
  2. 指针名 = new 类型[个数]; // 对象数组

  malloc函数返回的是void *类型,如果写成:ClassA* p = malloc (sizeof(ClassA));,程序则无法通过编译,会抛出这样的错误信息:“不能将 void* 赋值给 ClassA * 类型变量”。所以必须通过 (ClassA *) 来进行强制转型。相较而言,new则不存在强制转型的问题,而且书写更为简单。总结起来,malloc与new之间的区别主要有以下几点:

  new是C++运算符,而malloc则是C标准库函数。

  通过new创建的东西是具有类型的,而malloc函数返回的则是void*,需要进行强制转型。

  new可以自动调用对象的构造函数,而malloc不会。

new失败时会调用new_handler处理函数,而malloc失败则直接返回NULL。

  free与delete之间的区别则只有以下两点:

  delete是C++运算符,free是C标准库函数。

  delete可以自动调用对象的析构函数,而malloc不会。

  针对内置类型而言,因为没有对象的构造与析构,所以malloc/free除了需要强制转型之外,和new/delete所做的工作无异,用哪一个只是涉及个人喜好而已。

  1. //declaring native type  
  2. int* i1 = new int;  
  3. delete i1;  
  4.  
  5. int* i2 = (int*) malloc(sizeof(int));  
  6. free(i2);  
  7.  
  8. //declaring native type array  
  9. char* c1 = new char[10];  
  10. delete[] c1;  
  11.  
  12. char* c2 = (char*) malloc(sizeof(char)*10);  
  13. free(c2);

  既然提到了malloc/free,不能不提一下realloc。使用realloc函数可以重新设置内存块的大小,而在C++中没有类似于realloc这样的替代品。如果出现上述需求,所做的就是,释放原来的内存,再重新申请。

  既然new/delete的功能不仅赶上而且超越了malloc/free,那为什么C++标准中没有把malloc/free淘汰出局呢?这是因为C++要遵守“对C兼容”的承诺,要让一些有价值的包含malloc/free函数库的C程序在C++中得到重用。所以,在C++中,new/delete和malloc/free一直并存着。

  不过,将malloc/free和new/delete混合使用绝对不是什么好主意。Remember that, to new is C++; to malloc is C; and to mix them is sin. 如果用free来释放通过new创建的动态对象,或者用delete释放通过malloc申请的动态内存,其结果都是未定义的。换句话说,不能保证它会出现什么问题。如果程序在关键时刻就因为这个在重要客户面前出现问题,那么懊悔恐怕已经来不及了。

  请记住:

  (1)不要企图用malloc/free 来完成动态对象的内存管理,应该用new/delete。

  (2)请记住:new是C++的,而malloc是c的。如果混淆了它们,那将是件蠢事。所以new/delete必须配对使用,malloc/free也一样。

  建议22:灵活地使用不同风格的注释

  注释,可以说是计算机程序中不可或缺的一个部分,它的存在让我们阅读程序代码、理解作者意图变得相对容易(当然,这里说的是具有良好注释的代码)。在C/C++语言中,存在着两种不同的注释语法:

  旧有的C风格的注释: /* describe your purposes */

  新式的C++风格的注释: // describe your purposes

  既然两种注释语法都有效,选择哪一种呢?C风格的还是C++风格的呢?

  很多的C++书籍推荐我们使用新式的C++风格注释语法,比如受C++程序员顶礼膜拜的经典书籍《Effective C++》在条款4中的建议就是如此。为此,Scott Meyers还给出了一定的理由—由“内嵌注释结束符”引发的“惨案”:

  1. /* C风格的注释*/  
  2. if(a>b)  
  3. {  
  4.  /*    int temp =a;   /* swap a and b */  
  5.        a = b;  
  6.        b = temp;  
  7.  */  
  8. }  
  9.  
  10. // C++风格的注释  
  11. if(a>b)  
  12. {  
  13.          //int temp =a;   // swap a and b  
  14.          //a = b;  
  15.          //b = temp;  
  16. }

  当程序员因为某些特殊原因而采用C风格的注释语法将上述代码进行注释时,由于原代码中存在原有的内嵌注释,导致注释过早地找到结束匹配符,使代码注释失效,出现编译错误。而C++风格的注释则不会出现类似的麻烦。

 然而,正如一个硬币有两面,任何东西都是有利有弊的。让程序员更加便利与轻松才是硬道理。使用注释亦然。还是先看下面的一段代码:

  1. /***  
  2. * new.cxx - defines C++ new routine  
  3. *  
  4. *  Copyright (c) Microsoft Corporation.  All rights reserved.  
  5. *  
  6. *Purpose:  
  7. *      Defines C++ new routine.  
  8. *******************************************************/  
  9. #ifdef _SYSCRT  
  10. #include <cruntime.h> 
  11. #include <crtdbg.h> 
  12. #include <malloc.h> 
  13. #include <new.h> 
  14. #include <stdlib.h> 
  15. #include <winheap.h> 
  16. #include <rtcsup.h> 
  17. #include <internal.h> 
  18.  
  19. void * operator new( size_t cb )  
  20. {  
  21.   void *res;  
  22.   for (;;) {  
  23.       //  allocate memory block  
  24.       res = _heap_alloc(cb);  
  25.       //  if successful allocation, return pointer to memory  
  26.       if (res)  
  27.            break;  
  28.       //  call installed new handler  
  29.       if (!_callnewh(cb))  
  30.            break;  
  31.       //  new handler was successful -- try to allocate again  
  32.    }  
  33.    RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0));  
  34.    return res;  
  35. }  
  36. #else  /* _SYSCRT */

  这是VC++库中new.cpp文件中的部分代码。在注释方面,这段代码中有很多值得我们学习的地方。

  版权和版本声明,使用C风格的 /*  */

  标准化的代码有很多必不可少的东西,比如版权信息、文件名称、标识符、摘要、当前版本号、作者/修改者、完成日期、版本历史信息,等等。这些信息不会为我们的代码运行带来任何的改进,但是可以提高了代码的可读性,方便代码的维护。如此繁缛的信息,可能多达十几行,此时如果使用C++风格的注释语法,那么就得记得在每一行的开始都写下两个“/”符。那此时何不采用更加简单便利的/*  */呢?

  内嵌注释用 //

  内嵌注释一般出现在代码主体内。此时,建议使用新式的C++风格的注释语法。最直接的原因就是避免出现那些由“内嵌注释结束符”引发的“惨案”。不过,在这种情况下,出于调试原因用/* */注掉一块代码,也不会出现什么问题。

宏尾端的注释用/* */

  Scott Meyers对于注释语法的使用还提出了一个问题:一些“古董”级的、只针对C编译器而写的预处理器不能识别C++风格的注释,所以下面的代码就不能按照预期那样正常运行,它们会把注释当成宏的一部分:

#define LIGHT_SPEED 3e8  // m/sec (in a vacuum)

  虽然使用这样的“古董”预处理器的人近乎绝迹,但是保不齐会出现一个特例。所以为了保证百分之百不出错,建议在宏尾端的注释使用C风格的注释语法:

#define LIGHT_SPEED 3e8  /* m/sec (in a vacuum) */

  除此之外,还有一个特别的使用情形:默认参数函数的定义。代码片段如下所示:

  1. // 声明文件  
  2. class A  
  3. {  
  4. public:  
  5.      void Function( int para1, int para2 = 0 );  
  6. };  
  7.  
  8. // 实现文件  
  9. void A::Function( int para1, int para2 /* = 0*/ )  
  10. {  
  11.      // processing code  
  12. }

  我们一般将类的声明与实现进行分离,放置在不同的文件之中。此时如果函数存在默认参数,它只能出现在声明中,不过,在实现中缺少默认参数的说明可能会影响我们对函数的设计或理解,所以有必要在实现中对默认参数进行一些说明。使用C风格的注释语法按照上述形式进行说明确实是一个值得推荐的方式。在这种情形下,C++风格的注释变得无能为力了。

  灵活地使用两种形式的注释方式,在保证代码鲁棒性、可读性的同时,尽量使程序员获得更多轻松与便利。

  请记住:

  C风格的注释/* */与C++风格的注释//在C++语言中同时存在,所以我们可以充分地利用两种注释的长处,并注意可能存在的问题,这会让我们的编码变得更加轻松、便利、高效!



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值