EFFECT C++总结

来源:互联网 发布:淘宝怎么改评价 编辑:程序博客网 时间:2024/06/06 00:46

EFFECT C++总结

条款01 视c++为一个语言联邦
….好吧

条款02 尽量以const,enum,inline代替#define

1、用const常量代替define,方便调试,所使用的名字会进入符号表
2、常量指针 和class专属常量(static const int a = 5)
        在class中:属于声明,不是定义式(如果不用取得常量的地址,就不用定义,定义的时候也不用赋初值了,声明已经赋了)

3、enum hack(有些编译器不支持在class内赋初值)
        不可以取得enum的地址,跟define很像
4、对于define的函数..额…所有参数用个小括号括住…当然,inline template代替会更好~

条款03 尽可能使用Const
指针中,const处于不同位置:星号前面:被指物为常量,星号后面:指针为常量

const成员函数,两个函数的常量性不同,是可以重载的~(常量函数(不更改任何成员变量)和非常量函数)
【如果函数返回的是一个内置类型的返回值,修改这个返回值是不合法的】

mutable:成员变量将允许在常量函数被修改

非const函数可以调用const函数
不要让const函数调用非const函数

条款04  确定对象被使用前已先被初始化
内置型对象要手工初始化,C++不保证会初始化
确保每一个构造函数对每一个对象进行初始化

赋值 与 初始化的区别:
成员初值列属于初始化,在构造函数内执行的…是赋值~

成员初值列要列出所有成员

对static对象,确定哪个先初始化是很困难的,有必要的话用单例(local static)代替 0n

条款05 了解C++默默编写并调用那些函数
1、default构造函数:
2、析构函数:不是virtual,除非父类的析构函数是virtual)
3、复制构造函数:按成员类型:
        类类型(string):直接调用类(string)的构造函数
       内置类型:直接copy bits
4、赋值运算符重载:成员带引用的或者常量的,都拒绝生成,此时,没有重载运算符而且进行实例之间赋值操作的话,将会出现编译错误

条款06 若不想使用编译器自动生成的函数,就该明确拒绝
1、将想要禁止的函数声明为private(成员方法还是能够使用)
2、将想要禁止的函数声明为private,不实现(连接时错误)
3、将该类继承一个UnCopyable的类(该类的方法同样声明为private,不实现)【编译时错误,但是会导致多重继承】

条款07 为多态基类声明virtual析构函数
 1、一个要用作基类的class(尤其带virtual成员),其析构函数也要是virtual
2、不能所有类的析构函数都是virtual,你要弄个vtbr,占空间
3、不要继承不是设计为基类的构造函数(如STL(string,vector))他们的析构函数可没有virtual

条款08 别让异常逃离析构函数
 1、析构函数跑出异常可能会导致程序过早结束,或者其他不确定行为
2、跑出异常后有两种处理办法:记录异常并且结束程序,吞下异常
3、比较好的解决方案,弄一个函数,让用户自己选择:

void Close()
{
    db.close();
    closed = true;
}
~DBConn()
{
    if(!closed)
    {
        try{
            db.close();
        }
        catch{
            …..    
        }
|
    }
}

条款09 绝不让构造和析构过程中调用virtual函数
 1、在继承体系中,构造函数的执行顺序:父类 ->子类。在父类的构造函数,或者其调用的函数中调用virtual函数,此时,子类还没创建,所以还是会调用父类的virtual 函数,不会调用子类的
       同理,析构函数的执行顺序,是子类->父类。所以在调用父类的析构函数时,子类已经死掉…
2、解决方法:将子类的信息传给父类,让他来调用..:
    Transaction::Transcation(const std::string& info){ logTransaction(info);}

    BuyTransaction(…):Transaction(CreateLogString(…)){…}
   
    private:
        static std::string CreateLogString(…);

(static 很有讲究哦~声明为static,可以防止,初期为成熟之BuyTansaction对象内尚未初始化的成员变量)

条款10 令operator = 返回一个reference to *this
 1、为了连续赋值:(+=,-=,..都必须加哦~)
Widget& operator = (const Widget& rhs){
    ….
    return * this;
}

条款11 在operator= 中处理“自我赋值”
 1、加一个证同检测:
Widget& operator = (const Widget& rhs){
    if(this == &rhs)
        return *this;
   delete pb;    //Widget的成员
   pb = new BitMap(*rhs.pb);
    return * this;
}

2、记录原先的,并将delete放到赋值之后:
Widget& operator = (const Widget& rhs){
   BitMap* pOrig = pb;
   pb = new BitMap(*rhs.pb);
   delete pOrig ;    //Widget的成员
    return * this;
}

3、也可以用Swap
Widget& operator = (const Widget& rhs){
   Widget temp(rhs);
    swap(temp);
    return * this;
}

4、利用值传递简化代码…不过直观性不好
Widget& operator = ( Widget rhs){
    swap(rhs);    //由于值传递,此处的rhs已经是副本了
    return * this;
}

条款12 复制对象时,勿忘记每一部分
 1、复制对象所有成员变量,以及base class的变量(可以调用base的复制构造函数)
2、这个编译器不会报错哦

条款13 以对象管理资源
 1、避免潜在的资源泄露:
    获得资源后立即放进管理对象
    管理对象运用析构函数确保资源被释放
auto_ptr:
auto_ptr被销毁会自动删除它所指之物,所以一定要注意别让多个auto_ptr同时指向一个对象,为了预防这个问题,,auto_ptr有一个性质:若通过copy构造函数或者copy assignment操作符复制他们,他们会变为null,而复制所得的指针将获得资源的唯一拥有权.

引用计数型指针:RCSP
持续追踪多少个指针指向该资源

2、不要在array上用上述两种,因为他们都是在析构函数直接delete,而不是delete[]

条款14 在资源管理类中心小心copy行为
1、auto_ptr和RCSP只适合对于heap_base的资源管理
2、对于非heap_base的,又适用于RAII(资源取得时机就是初始化时机),当出现复制会怎么样?:
    (1)、禁止复制
    (2)、对底层资源祭出“引用计数法”(指定删除器,不一定是delete,有可能是unlock。。。)
    (3)、复制底部资源(深度拷贝)
    (4)、转移底部资源的拥有权(就是一复制,原先的置为null) 

条款15 在资源管理类中提供对原始资源的访问
1、auto_ptr和RCSP只适合对于heap_base的资源管理
2、对于非heapbase类型则要给RAII提供获取他们的函数,有两种方法,显式转换(直接get),麻烦但是安全,隐式转换,方便,但是容易被人误用,不安全

条款16 成对使用new和delete时要采取相同形式
1、用了new就要用delete
2、new对应delete, new一个数组对应delete[](尤其是喜欢用typedef的要注意)
3、对于new:就只有一块内存,但是new一个数组,y有n+1块内存,第一块放的是数组长度…
    如果delete[] 一个非数组,new出来的第一块内存就被读成数组长度m,然后执行m次析构函数
    如果delete一个数组,则仅仅数组长度的内存被释放…其余的内存块都不会释放

条款17 以独立语句将newed对象置入智能指针
1、考虑以下的代码:process(std::trl::shared_ptr(new Widget),priority());
咋眼一看,看似非常正确,但是,这里面可能会出现内存泄露,因为对于上述函数,C++代码经过编译器优化之后的执行顺序是不确定的:
假设是先执行new Widget ,再执行priority(),再执行share_ptr的构造函数…当priority()执行失败时,就会出现资源泄露,因为他没有成功放进share_ptr中,所以记得:

std::trl::shared_ptr pw = std::trl::shared_ptr(new Widget);    //以一个独立语句(不仅仅像上述情况,其他情况也要以一条独立语句将对象放进智能指针)
process(pw,priority());
这样就没问题了,编译器对于独立语句没有干涉顺序的权利

条款18 让接口容易被正确使用,不易被误用
1、类型系统
2、尽量令你的types跟内置types一致
3、提供一致的接口
4、提供每个指针专属的删除器…消除资源管理的责任,防范DLL问题

条款19 设计class犹如设计type
1、新type对象应该如何被创建销毁
2、对象的初始化和对象的赋值该有什么样的差别
3、新type的对象如果被值传递意味着什么
    copy构造函数用来定义一个type的pass-by-value该如何实现
4、新type的合法值
5、新tpye需要继承吗
6、新type需要什么转换
7、什么样的操作符和函数对此新type而言是合理的
8、什么样的标准函数需要驳回
9、谁该取用新type的成员
10、什么是新type的“未声明接口”
11、你的新type有多么一般化
12、你真的需要新type吗?

条款20 宁以pass-by-reference-to-const 替换pass-by-value
1、pass-by-value消耗性能..
2、reference其实往往以指针实现
3、不适合内置类型

条款21 必须返回对象时,别妄想返回其reference
1、函数创建新对象的途径:
    在stack空间创建:定义一个local变量 —— Rational result;
    在heap空间创建:用new对象~
2、返回一个heap空间的对象也是有问题的…因为这会导致用户不知道怎么样对它实施delete
3、返回一个static则会导致函数不可以同一行出现多个…

条款22 将成员变量声明为private
1、方便控制变量的读写访问权限
2、日后做改变不会牵涉到其他类
3、protected变量与private一样缺乏封装性…当要将一个变量删除之后…就会牵涉到他的子类啦,代码要重写,重新测试…

条款23 宁以non-member、non-friend替换member函数
1、member函数会增加对私有变量的访问渠道,所以封装性低
2、通常做法,让该函数作为非成员函数,但是放在同一个命名空间(STL就是这么做的,用户想用哪个就include哪个即可,降低编译依赖度)

条款24 若所有参数皆需要类型转换,请为此用non-member函数
1、当我们想重载一些类似运算符的时候,应该用非成员函数,因为进入这种函数的参数都会经过类型转换,如果是成员函数:
就会出现
result = oneHalf * 2;可以(oneHalf重载了Operator*)
result = 2 * oneHalf(2没有重载啊,如果是非成员的话,2也重载了~)

条款25 考虑写一个不抛异常的swap函数
1、缺省的swap:
void swap(T& a ,T& b)
{
    T temp(a);
    a = b;
    b = temp;
}
不足:三次复制,没必要
2、置换指针即可,将指针指向另一个对象(办法:偏特化~!!只能针对class template,不能对function template)
namespace WidgetStuff{
    template
    class Widget{
    public:
        void swap(Widget& other)
        {
               using std::swap;
                swap(pImpl,other.pImpl);    //仅仅改变指针
        }
        Widget& operator = (const Widget& rhs)
        {
               ….
                pImpl = (rhs.pImpl);
               ….
        }
    private:
        WidgetImpl* pImpl;    
    };
    
    template
    void Swap(Widget& a,Widget& b)        //声明一个nonmember template函数来调用偏特化函数
    {    
        a.swap(b);
    }
}
 3、不要尝试在std加入新的东西哟~

条款26 尽可能延后变量定义式的出现时间
1、程序控制流到达某个变量定义式时,需要承受构造成本,离开某个作用域时,你要承受析构成本
2、当我们定义变量后,由于异常丢出,有可能这个变量就没有用到,但我们白白承受了构造成本
3、在构造的时候就直接给初值会更好(条款4)
4、我们的延后不仅仅到他要使用的时候,而是延后到他要初始化的时候
5、对for循环一些特殊的…就不必纠结这个条款

条款27 尽量少做转型动作
1、转型意味需要一个偏移量,意味知道对象如何布局,这在不同平台上是个问题
2、转型会导致成员函数的隐藏this的函数
3、可以寻找代替转型的方法:使用类型安全容器或者将virtual函数往继承体系上方移动
4、避免连串转型

条款28 避免返回handles执行对象内部部分
1、想这样的函数是错误的:
    Point& upperLeft()const {return pData->ulhc;}
别人可以很轻松地通过该函数获取并修改类私有成员
2、就算返回加上const,也有可能发生空悬指针的问题
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());
当函数结束时,临时对象&(boundingBox(*pgo),将会被delete,结果就是pUpperLeft指向一个不存在的对象

条款29 为异常安全而努力是值得的
1、异常安全性:
    不泄露任何资源
    不允许数据破坏
异常安全函数提供以下三个保证之一:
1、基本承诺:如果异常抛出,程序内任何事物仍然保持在有效状态下
2、强烈保证:如果异常抛出,程序状态不改变,程序回到调用函数前
3、不抛掷保证:函数不会抛出异常,像内置类型就是这样

达到强烈保证有一种策略copy and swap:为需要修改的东西弄一个副本,修改成功才替换,通常用pImpl idiom实现,将源对象放进一个对象,然后给原对象一个指针指向实际对象
虽然如此但是不能保证整个函数有强烈的异常安全性..因为有可能他会调用其他异常不安全的函数

他的最高级别只能是所有调用函数的异常级别最弱者

条款30 透彻了解inlining的里里外外
1、inline会增加目标码的大小,过度使用inline会使得程序体积太大,带来的代码膨胀会导致额外的换页行为(paging),降低高速缓存装置的击中率..\
2、换个角度,如果inline函数本体很小,编译器针对函数本体的产生码比针对函数调用的产出码还小,用inline就可以减少目标代码,从而提高cache命中率
3、inline的隐喻式:将函数定义在class定义式内
4、对于template,如果不打算具现化的所有函数都是inline就不应该用inline template
5、大多数编译器拒绝将太复杂的函数inline,对virtual函数的inline也几乎是无效的
6、编译器通常不会对通过函数指针而进行的调用inline
7、对构造函数和析构函数进行inline:
    1、构造函数和析构函数会被编译器插入一些我们看不到的代码
    2、Base构造函数inline,意味着他要被插入到他的子类的构造函数中,如此类推
8、使用inline函数会导致,一旦有任何修改需要重新编译…一整个,而不是重新链接(尤其是一个程序库有一个inline函数f,被一个程序用到..f改…整个程序需要重新编译…)

条款31 将文件间的编译依存关系降至最低
1、include,为了让编译器知道这个变量的具体大小,好分配内存。如果仅仅用指针,则声明一下class就好(前置声明)
2、include会导致编译依赖
3、标准头文件,不太可能成为编译瓶颈,尤其是当建置环境允许你使用编译头文件
4、为了不用指针切不用#include引起不必要的编译依赖,我们采用pimpl idiom方法,将include扔到接口处,然后在class实现该接口,并保存一个指向接口的指针,所有实现方法均通过接口进行
5、编译依存最小化的设计策略:
    1、如果使用object references或object pointers可以完成任务,就不要用objects
    2、如果能够,以class声明式代替class定义式
    3、为声明式和定义式提供不同的头文件:参考STL的,只含声明式,还有关键字export

条款32 确定你的public继承塑模出is-a关系
1、特例:企鹅与鸟,正方形与长方形…
2、public需要的是父类拥有的行为,子类都拥有,不是简单的常识关系

条款33 避免遮掩继承而来的名称
1、遮掩baseClass的名称,其实是违反了is-a规则,因为她有可能将baseclass的函数遮掩了,相当于子类没有拥有这些行为
2、遮蔽了可以利用using来让他们重见天日:
    using base::fx;
3、当你不想让子类继承所有父类的函数时,可以利用private继承 + 转交函数:
class base{
public:
    virtual void mf1() = 0;
    virtual void mf1(int);
    ….
};
class Derived:private Base{
public:
    virtual void mf1()
    {Base::mf1();}
    ….
};
Drived d;
int x;
d.mf1();
条款34 区分接口继承和实现继承
1、接口继承:不重写方法 实现继承:可重写方法
2、pure virtual函数只具体指定接口继承
3、简朴的(非纯)inpure virtual 函数具体指定接口继承以及缺省实现继承
4、non-virtual 函数具体指定接口继承以及强制性实现继承
条款35 区分接口继承和实现继承
1、由Non-Virtual Interface手法实现TemplateMethod模式(模板模式)
class GameCharacter
{
public:
    int healthValue() const
    {
            int  retVal = doHealthValue();
            return retVal;
    }
private:
    virtual int doHealthValue() const{….}
};
缺点:当该函数需要让一个兄弟作为参数时,就不能private定义virtual函数了
2、由Function Pointers实现Strategy模式
3、由tr1::function实现Strategy模式(template) 不错呦
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:
        typedef std::tr1::function

0 0
原创粉丝点击