effective C++总结(转)

记得大一C++学得比较到位,现在忘得差不多了,有空看看别人的总结,来的比较快。。。

转自:http://blog.chinaunix.net/u2/75985/showart.php?id=1219076

 

 

导读
如果没有什么训练和素养,就贸然使用C++,会导致写出来的代码不易理解,不宜维护,不宜扩充,缺乏效率,而且容易出错。
 
Scott Meyers在狠夸了C++语言怎么好之后,抛出了这么一句,点名了使用C++的难度,我们在读一些前辈的代码时,有时经常觉得难懂,这是否属于不易理解、不宜维护呢?其实不然,C++语言引入继承和多态后,使语言更加具有隐藏性,层次更加复杂!特别是使用了多种设计模式的程序,对于没有过训练的程序员更加艰涩难读。
那都需要什么训练呢?之一,细读一本C++教程后,读这本书,然后深入实践C++,“找出C++可能绊倒你的障碍,学习避开它们”;之二,学习常用设计模式,理解高手为了易于扩展而使用类层次;之三,多看库的源码,理解库的类设计和层次,如STL、BOOST、ACE等。

1、拷贝构造函数被调用:
第一种:
test b;
test a = b;
 
第二种:
传值方式将对象传递给方法作参数或返回
test operator+(const test a, const test b);
 
2、构造函数通常必须检验其参数的有效性,而大部分赋值运算符不必如此,因为其参数必然合法(检验已在它的构造函数中完成)。
 
3、语言内建的关系运算符(如>、<、==)的返回类型都是bool类型
 
<改变旧有的C习惯>
1.尽量以const和inline取代#define
C语言用#define可以做到两点,第一,定义常量,如#define PI 3.1415926或#define NAME "Jade";第二,定义宏表达式(函数),如#define min(a,b) (a)<(b)?(a):(b)。
但是这样做会有些问题,首先,不易调试,因为这个宏在预编译时就被替换为实际代码了,跟踪器一般都很难跟踪(inline函数一样不能被跟踪!见条款33);其次,使用宏表达式,虽然样子像函数,但是由于会被替换为实际代码,行为又和函数不同,容易造成错误。
因此,建议使用const变量和inline函数替换宏的这两个功能。
 
const double PI = 3.1415926;
const char NAME[] = "jade";
inline min(a,b) { if (a < b) return a; else return b; }
 
class A
{
public:
    static const int N;
};
 
const int A::N = 100;
 
2.尽量以<iostream>取代<stdio.h>
Scott Meyers建议我们使用<<和>>代替printf和scanf函数。
 
因为只要一个自定义类型实现了<<操作符(friend ostream& operator<<(ostream &os, const ClassName &r);),就可以使用<<打印自定义类型的内容,这是用printf所无法做到的。

<iostream>是在std名字空间里的函数,而<iostream.h>中的函数是全局函数,没有名字空间

3.尽量以new和delete取代malloc和free
如果使用new获得的指针,就使用delete释放它;如果用malloc申请的空间,就用free释放它,千万不要混用!

4.尽量使用C++风格的注释形式
C语言风格注释/**/不支持嵌套

<内存管理>
5.使用相同形式的new和delete
使用new[]生成对象数组,释放时也要使用delete[],如果使用了delete,则除第一个对象的析构函数被调用外,其他对象的都没有被调用

6.记得在destructor中以delete对付指针成员
有point成员的类中,应做到:
1、在每一个构造函数中将该指针初始化。如果没有任何一个构造函数会将内存分配给该指针,那么指针应该初始化为NULL。
2、在=操作符中将指针原有的内存删除,重新分配一块。
3、在析构函数中delete这个指针(delete一个NULL指针是安全的),除非这个指针不是在这个类中被new出来的,除了smart point。
 
 
7.为内存不足的状况预作准备
当C++使用new分配内存出现内存不足的情况时,会抛出std::bad_alloc异常(不使用new(nothrow)形式,返回NULL),应该捕捉这个异常进行处理。另外还可以设置一个分配内存失败时的回调函数,在头文件<new>中提供了一个函数set_new_handler用来注册这个回调函数,原型如下:
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
参数为注册的回调函数,返回为原来的回调函数。
 
如果注册了回调函数,在new抛出bad_alloc异常前,会回调这个函数,如下例所示:
#include <memory>
using namespace std;
 
void func()
{
    set_new_handler(NULL);  //如果不把处理函数设为空,它会被new不停地调用,直到分配内存成功
    cout << "call func()" << endl;
}

int main(int argc, char *argv[])
{
    new_handler f = set_new_handler(func);
 
    try
    {
        char *p = new char[0x7fffffff];
    }
    catch(bad_alloc&)
    {
        set_new_handler(f);
        cout << "get exception" << endl;
    }
    getchar();
 
    return 0;
}
 
会打印出:
call func()
get exception
 
Scott Meyers并对set_new_handler函数行为作出了自己的建议:
1、让更多的内存可用,在一开始分配出一块大的内存,当发生内存不足时,释放此内存。
2、安装不同的new_handler,改变new_handler的行为。
3、卸载new_handler,即再次调用set_new_handler指针p传NULL,这样就不会每次发生内存不足重复调用new_handler了,它会抛出std::bad_alloc异常。
4、抛出自己的异常,可以抛出std::bad_alloc或它的派生类。
5、不返回,直接调用abort或exit函数。
 
也可以使每个类拥有自己的new_handler,这需要重载new操作符。因为这个new操作符和new_handler的行为都是相同的,所以可以定义为一个模板,让其他类继承它,得到这些接口。
 
template<class T>
class NewHandlerSupport
{
public:
    static new_handler set_new_handler(new_handler p);
    static void *operator new(size_t size);
private:
    static new_handler currentHandler;
};
 
template<class T>
new_handler NewHandlerSupport<T>::set_new_handler(new_handler p)
{
    new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
}
 
template<class T>
void* NewHandlerSupport<T>::operator new(size_t size)
{
    /* 使全局new的handler为自己的new_handler */
    new_handler globalHandler = std::set_new_handler(currentHandler);
 
    void *memory;
    try
    {
        memory = ::operator new(size);
    }
    catch (std::bad_alloc&)
    {
        std::set_new_handler(globalHandler);
        throw;
    }
 
    std::set_new_handler(globalHandler);
    return memory;
}
 
//使得每一个currentHandler为0
template<class T>
new_handler NewHandlerSupport<T>::currentHandler;
 
使用举例:
class X: public NewHandlerSupport<X> {
 
};
 
void noMoreMemory();
X::set_new_handler(noMoreMemory);
X *p = new X[N];     //内存不足
 
其实这样使用起来也挺麻烦,可以直接在类内定义自己的new_handler方法(作为一个抽象方法),不用set_new_hander直接使用,就更加方便了。
 
8.撰写new和delete操作符时应遵守公约
自己编写的new操作符的行为应与缺省的new操作符保持一致,就像上一个主题讲的那些new的特征:
1、当分配内存成功时,返回内存的地址;分配失败,如果注册了处理函数,则会调用此函数,(如果没有退出)并一再尝试分配内存,并在每次失败后再调用此函数,直到分配内存成功或处理函数中抛出异常或将注册的处理函数撤消(设为NULL,这时再分配失败则按默认方式处理(见后));如果没有注册处理函数,则抛出bad_alloc异常。
2、如果用户要求分配0字节内存,就给他分配1字节并返回此地址
3、没有3了吧?
 
Scott Meyers提供的伪代码说明:
void *operator new(size_t size /* ,... */)
{
    if (size == 0)
        size = 1;
 
    while (true)
    {
        尝试分配size个字节的内存;
        if (分配成功)
        {
            return 内存的地址;
        }
 
         //由于不能访问当前配置的handler,所以使用这种方法获得
        new_handler globalHandler = set_new_handler(0);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值