Effective C++55条款速记版(下)

  • 26.尽可能延后变量定义式的出现时间——这样可增加程序的清晰度并改善程序效率
  • 27.尽量少做转型动作——C++规则设计目标之一是保证“类型错误”绝不可能发生。C++提供四种新型转型:
    const_cast(expression)
    dynamic_cast(expression)
    reinterpret_cast(expression)
    static_cast(expression)
每种转型的作用如下: 
1.const_cast通常被用来将对象的 常量特性转除(cast away the constness)。它也是唯一由此能力的C++-style转型操作符。 
2.dynamic_cast主要用来执行“安全向下转型”(safe downcasting),也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一 可能耗费重大运行成本的转型动作(后面细讲)。 
3.reinterpret_cast意图执行低级转型,实际动作(结果)可能 取决于编译器,这表明其 不可移植。例如将pointer to int转为int,这类转型常用在低级代码。例如,讨论讨论如何针对原始内存(raw memory)写一个调试用的分配器(debugging allocator),见条款50. 

4.static_cast执行强迫隐式转换(implicit conversions)。例如将int转为double,non-const转为const等。它也可以用来执行一些转换的反向转换,如将void*指针转为typed指针,将pointer-to-base转为pointer-to-derived。但无法将const转为non-const,这个只有const_cast才能办到。

  • 28.避免返回handles(号码牌用来取得某个对象如指针,references,和迭代器)指向对象内部成分——遵守这个条款可增加对象封装性,帮助const成员函数行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性降至最低。
  • 29.为“异常安全”而努力是值得的——异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏。
  • 30.透彻了解inlining的里里外外(inline函数)——将大多数inlining限制在小型、被频繁调用的函数身上。不要因为function templates出现在头文件,就将他们声明为inline。
  • 31.将文件间的编译依存关系降至最低——(1)如果使用object references或object pointers可以完成任务,就不要使用object。(2)如果能够,尽量以class声明式(class Date;)替换class定义式(通过#include完成)。(3)为声明式和定义式提供不同的头文件。
  • 32.确定你的public继承塑模出is-a关系——适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象。
  • 33.避免遮掩继承而来的名称——关于同名的变量或函数在继承体系中的调用问题如:
    class Base
    {
         public:
         virtual void mf1();
         virtual void mf1(int);
         virtual void mf2();
         void mf3();
         void mf3(int);
         void mf4();
    };
    
    class Derived: public Base
    {
         public:
         virtual void mf1();
         virtual void mf2();
         void mf3();  
    };
    
    Derived d;
    int x;
    d.mf1();//调用Drived::mf1();
    d.mf1(x);//调用失败Base中的mf1(int)被Drived::mf1()函数隐藏。
    d.mf2();//调用Drived::mf2();
    d.mf3();//调用Drived::mf3();
    d.mf3(x);//调用失败Base中的mf3(int)被Drived::mf3()函数隐藏。
    d.mf4();//调用Base::mf4();
由上面的代码可知,派生类中的函数隐藏了所有在基类中的同名函数,无论函数参数列表是不是相同。 
为了解决这种问题,只要在public作用域下调用using声明就能解决这种问题。如下代码:
class Base
{
     public:
     virtual void mf1();
     virtual void mf1(int);
     virtual void mf2();
     void mf3();
     void mf3(int);
     void mf4();
};

class Derived: public Base
{
     public:
     using Base::mf1;
     using Base::mf3;
     virtual void mf1();
     virtual void mf2();
     void mf3();  
};

Derived d;
int x;
d.mf1();//调用Drived::mf1();
d.mf1(x);//调用Base中的mf1(int)。
d.mf2();//调用Drived::mf2();
d.mf3();//调用Drived::mf3();
d.mf3(x);//调用Base中的mf3(int)。
d.mf4();//调用Base::mf4();
public 继承的意义是派生类继承基类的所有,包括函数和变量,为了达到拥有并使用的目的,我们必须记住以上所说的特性。
  • 34.区分接口继承和实现继承——(1)声明一个pure virtual函数的目的式未来让derived classes只继承函数接口。(2)声明非纯impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。(3)声明non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现。
  • 35.考虑virtual函数以外的其他选择——(1)通过NVI(non-virtual interface)手法--“通过public non-virtual成员函数简介调用private virtual函数”(2)将virtual函数替换为“函数指针成员变量”(3)以tr1::function成员变量替换virtual函数。(4)将继承体系内的virtual函数磁环为另一个继承体系内的virtual函数。
  • 36.绝不重新定义继承而来的non-virtual函数——non-virtual函数式静态绑定而virtual函数却是动态绑定。
  • 37.绝不重新定义继承而来的缺省参数值——因为缺省参数值都是静态绑定,而virtual函数(你唯一应该覆写的东西)却是动态绑定。
  • 38.通过复合塑模出has-a或“根据某物实现出”——复合的意义和public继承完全不同。
  • 39.明智而审慎地使用private继承——派生类对象不能隐式转换为基类对象,private继承在基类和派生类之间已经没有所谓的继承关系,之所以有它的存在纯粹是为应用层面服务,即为了编程的灵活性而存在的继承关系。private继承意味着只有实现部分被继承,接口部分应略去。尽量使用复合,必要时才使用private继承。private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。
  • 40.明智而审慎地使用多重继承——1.使用virtual继承的对象比non-virtual继承的对象体积大 2、访问virtual base classes成员变量比访问non-virtual base classes成员变量速度慢。对于菱形继承关系,可以使用virtual关键字,避免重复代码。
  • 41.了解隐式接口和编译器多态——编译器多态是由于模板参数在编译器具现化而产生的,运行期多态是由于virtual继承产生。隐式接口基于有效表达式显式接口由函数的签名式(也就是函数名称、参数类型、返回类型)构成。
  • 42.了解typename的双重意义——当声明template类型参数,class和typename的意义完全相同,只有在嵌套从属类型名称的时候使用typename,但是不得在base class lists或成员初值列内用它作为base classes修饰符。
  • 43.学习处理模板化基类内的名称——如何定义并使用关于模板类的派生过程,如何处理派生过程出现的编译不通过问题。有三种方式:
    template 
    class B:public A
    {
    public:
        void func3()
        {
            this->func1();//加上this关键字
        }
    };
    template 
    class B:public A
    {
          using A::func1;
    public:
        void func3()
        {
            this->func1();//加上this关键字
        }
    };
    template 
    class B:public A
    {
    public:
        void func3()
        {
            A::func1();
        }
    };第三种会关闭virtual绑定行为。
  • 44.将与参数无关的代码抽离template——1、template生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的tempalte参数产生相依关系。2、因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或者class成员变量替换template参数。 3. 因类型而造成的代码膨胀,也可以降低,做法是让带有完全相同二进制表述的具现类型共享实现码。
  • 45.运用成员函数模板接受所有兼容类型——成员函数模板(member  function templates)作用是为class生成函数。泛化构造函数:根据对象u创建对象t,而u和v的类型是同一个templates的不同具现体。
    template
        class SmartPtr{
        public:
            template
            SmartPrt(const SmartPrt& other)
            :heldPrt(other.get()){};
            T* get() const{return heldPrt;}
            ……
        private:
            T* heldPrt;
        };
    
        SmartPtr pt1=SmartPtr(new Middle);
        SmartPrt pt2=SmartPrt(new Bottom);
        SmartPrt pct2=pt1;
    46.需要类型转换时请为模板定义非成员函数——当我们编写一个class template,而它提供“与此template相关的”函数支持“所有参数隐式类型转换”时(参见条款24),请将这些函数定义为“class template”函数内部的friend函数
  • 47.请使用traits classes表现类型信息——1、traits classes使得“类型相关信息”在编译器可用,他们以templates和“templates特化”完成实现。2、整合重载技术后,traits classes 有可能在编译器对类型执行if...else测试。
  • 48.认识template元编程——1、模板元编程(TMP)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。2、TMP可被用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不合适的代码。
  • 49.了解new-handler的行为——在operator new抛出异常以前,会先调用一个客户指定的错误处理函数:new-handler。当内存分配失败的时候如何自定义并使用这个内存异常处理函数。关键语句就是set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
  • 50.了解new和delete的合理替换时机——什么时候需要替换编译器提供的operator new或delete?1、用来检测运用上的错误。2、为了强化效能。3、为收集使用上的统计数据。见详解4、为了增加分配和归还的速度。5、为了降低缺省内存管理器带来的空间额外开销。6、为了弥补缺省分配器的非最佳对齐7、为了将相关对象成簇集中8、为了获得非传统的行为。
  • 51.编写new和delete时需固守常规——1、我们定义new的时候需要把size为0的内存申请考虑进去,当内存申请为0时分配1个字节的内存。2、由于base class一般都会比derived class小,所以,当申请大小不满足derived class时,我们调用标准的内存分配器。3、对于class专属版的arrays内存分配,需要实现operator new[]4、C++保证删除指针永远安全,所以在程序中,我们对于null指针的操作如下代码:
    void operator delete(void* rawMemory) throw()
    {
        if(rawMemory==0) return;
    
        归还rawMemory所指内存;
    }
  •         5、我们要始终记得new和delete的对立,new的处理方式和delete的处理方式要交相呼应。
    void Base::operator delete(void rawMemory, std::size_t size) throw()
    {
        if(rawMemory==0) return;
        if(size!=sizeof(Base)){
            ::operator delete(rawMemory);
            return ;
        }
        归还rawMemory所指内存;
        return ;
    }
    • 52.写了placement new 也要写placement delete——placement new的用途之一就是负责在vector的未使用空间上创建对象。1.对于语句Widget* pw=new Widget;来说,该语句做了两件事情,第一件事情是申请了内存区域;第二件事情是在该内存区上进行对象的构造,即调用构造函数。我们设想其中一种执行情况,当第一件事情完成,而第二件事情出现异常,那么我们应该怎么去处理,很显然我们需要将这部分内存还原回去,于是如何还原回去成了我们需要解决的问题。2. 术语placement new意味着带有额外参数的new,如void* operator new(std::size_t, void* pMemory) throw();,placement delete味着带有额外参数的delete。3.如果一个带额外参数的operator new 没有“带相同额外参数“的对应版operator delete, 那么当new的内存分配动作需要取消并恢复旧观时旧没有任何operator delete会被调用,因此,为了消除稍早中的内存泄漏,widget有必要声明一个placement delete。4.””placement delete只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。对一个指针施行delete绝不会导致调用placement delete。 这意味对所有placement new我们必须同时提供一个正常的delete和一个placement delete版本
    • 53.不要轻易忽略编译器的警告——1、严肃对待编译器发出的警告信息。努力在你的编译器阿德最高警告级别下争取“无任何警告“的荣誉。2、不要过度依赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移植到另一个编译器上,你原本依赖的警告信息有可能消失。
    • 54.让自己熟悉包括TR1在内的标准程序库——C++98列入C++标准程序库的主要成分有:1、STL,覆盖容器(containers如vector,string,map)、迭代器(iterators)、算法(find, sort,transform)、函数对象(less,greater)、各种容器适配器(如stack,priority_queue)和函数对象适配器(mem_fun, not1)。2、Iostreams,覆盖用户自定义缓冲功能、国际化I/O以及预先定义好的对象cin,cout,cerr和clog。3、国际化支持,wchar_t和wstring等4、数值处理,复数模板和纯数值数组。4、异常阶层体系。5、C89标准程序库。TR1组件实例有:智能指针、一般化函数指针(tr1::function)、hash-based容器、正则表达式、tr::bind等
    • 55.让自己熟悉Boost——Boost网址
    • Effective C++书籍下载

    你可能感兴趣的:(C++相关书籍)