zoukankan      html  css  js  c++  java
  • effect C++笔记

    视C++为一个语言联邦

    C
    Object-Oriented C++
    Template C++
    STL
    对内置(也就是C-like)类型而言pass-by-value通常比pass-by-reference高效,但当你从C part of C++移往Object-OrientedC++,由于用户自定义(user-defined)构造函数和析构函数的存在, pass-by-reference-to-const往往更好。

    尽量以const,enum,inline替换#define

    对于单纯常量,最好以const对象或enums替换#defines

    使用const创建class专属常量

    通常C++要求你对你所使用的任何东西提供一个定义式,但如果它是个class专属常量又是static且为整数类型(integral type,例如ints,chars,bools),则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无须提供定义式。但如果你取某个class专属常量的地址,或纵使你不取其地址而你的编译器却(不正确地)坚持要看到一个定义式,你就必须另外提供定义式如下:

    const int GamePlayer::NumTurns;
    

    请把这个式子放进一个实现文件而非头文件。由于 class 常量已在声明时获得初值(例如先前声明NumTurns时为它设初值5),因此定义时不可以再设初值。

    可以使用const创建一个class的专属常量,但却不能使用#define创建一个专属常量

    为了将常量的作用域(scope)限制于class内,你必须让它成为class的一个成员(member):

    class GamePlayer
    {
    private:
          static const int Num=5;//常量声明式
          int scores[Num];//使用该常量
          ...
    };
    

    通常C++要求你对你所使用的任何东西提供一个定义式,但如果它是个class专属常量又是static且为整数类型(integral type,例如ints,chars,bools),则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无须提供定义式。但如果你取某个class专属常量的地址,或纵使你不取其地址而你的编译器却(不正确地)坚持要看到一个定义式,你就必须另外提供定义式如下:

    const int GamePlayer::Num;//Num的定义式,下面告诉你为什么没赋值
    

    请把这个式子放进一个实现文件而非头文件。由于 class 常量已在声明时获得初值(例如先前声明NumTurns时为它设初值5),因此定义时不可以再设初值。
    如果你的编译器不支持上述语法,你可以将初值放在定义式:

    class ConstEstimate{
    private:
          static const int Num;
    };//位于头文件内
    
    const int Num=5;//位于实现文件内
    

    使用enum创建类内常量

    如果你的编译器不允许使用static const创建类内常量,那么可以使用enum来代替:

    class GamePlayer{
          enum {Num=5};
          int scores[Num];
    };
    

    需要注意的是,enum常量是不能被取址的。

    对于形似函数的宏(macros),最好改用inline函数替换#defines

    使用# define创建一个比较大小的宏:

    #define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
    

    需要注意的是所有实参都需要加上小括号

    以上函数有着很多缺点,但优点是不需要调用函数的开销。

    使用inline内联函数,不仅可以获得#define的效率,还可以获得函数编译和检查。
    因此,对于形似函数的宏(macros),最好改用inline函数替换#defines。

    内联函数inline和函数的区别
    内联函数会进行副本替换,会存在多个副本占用多份内存,而函数只有一个地址只占用一份内存,但函数调用会产生压栈,速度会降低,而内联函数是直接将代码嵌入到调用处,省去了压栈寻址的过程,这是典型的空间和时间转换的问题。

    const 语法虽然变化多端,但并不莫测高深。如果关键字 const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。

    以下两种方式是相同的,都表明指针所指向的内容不可以被改变

    void f1(const Widget* pw);
    void f2(Widget const* pw);
    

    尽可能使用const

    将某些东西声明为 const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体

    如果关键字 const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。

    以下写法是等价的,都是限制指针所指内容是常量的:

    void f1(const int * pw);
    void f2(int const * pw);
    

    使用const修饰函数返回值时,如const引用,则可以既获得返回值传递的高效性,也可以获得返回值的常量性。

    编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)

    const成员函数
    const修饰成员函数,作用是限制对象内成员函数不修改任何成员变量,需要注意的是,在定义和实现中都需要写上const
    此时函数内部如果发生变量被改变的行为都会导致编译错误

    得知哪个函数可以改动对象内容而哪个函数不行,很是重要。
    C++定义,bitwise constness是指成员函数不改变任何成员变量。
    但是如果成员函数改变了指针所指物,编译器却也能通过编译,这个问题引出了logical constness概念。
    因此以上叙述的是C++定义的成员函数是只要成员函数不更改成员变量,就是const成员函数,而logical constness认为,如果返回值可以被更改,则不属于const成员函数,因为这种方式虽然不是通过成员函数更改了成员变量,但最终还是通过返回值更改了成员变量,这是不被认为是const成员函数的。
    如果const成员函数希望能更改某些成员变量,那么这个成员变量应该用mutable进行修饰。

    当 const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复

    Class TextBlock{
    Public:
    	…
    	const char & operator[] (std::size_t position) const
    	{
    		…
    		…
    		…
    		return text[positon];
    	}
    	char& operator[] (std::size_t position)
    	{
    		return cosnt_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
    	}
    	…
    };
    

    这里共有两次转型:第一次用来为*this添加 const(这使接下来调用 operator[]时得以调用 const版本),第二次则是从const operator[]的返回值中移除const。

    更值得了解的是,反向做法——令 const版本调用 non-const 版本以避免重复——并不是你该做的事。

    条款04:确定对象被使用前已先被初始化

    为内置型对象进行手工初始化,因为C++不保证初始化它们。

    为了减少初始化造成的消耗,c语言会允许变量未初始化。
    因此array不保证初始化,而vector却可以保证初始化

    构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。

    在构造函数中,一定要使用成员初始化列表列出所有成员变量,即使有些变量不需要赋初值,以免某些变量被忘记赋值。
    使用成员初始化列表的原因是,如果不使用成员初始化列表,而是在构造函数中赋值,将会面临的过程是,首先调用了默认构造函数,其次调用了赋值运算符,而如果使用成员初始化列表,将会只调用一次复制构造函数。

    许多class拥有多个构造函数,这时如果每个构造函数都去列表初始化一次成员变量,则会造成代码冗余,解决这个问题的方法可以是,将内置类型的初始化用赋值方式代替,并封装到函数内,然后在每个构造函数的代码块内调用该函数。因为对于内置变量而言,这两种方式的消耗是差不多的。

    //.h文件
    class People{
    public:
          People(const string& name,const string& dress);
    };
    
    //.c文件
    People(const string& name,const string& dress) //低效写法,会先调用默认构造函数再调用赋值运算符
    {
          name="XiaoMing";
          adress="TianJin";
    }
    People(const string& name,const string& dress)//成员初始化列表
    :name("XiaoMing"), adress(TianJin)
    {
    }
    

    C++有着十分固定的“成员初始化次序”。是的,次序总是相同:base classes更早于其derived classes被初始化(见条款12),而class的成员变量总是以其声明次序被初始化。即使它们在成员初值列中以不同的次序出现(很不幸那是合法的),也不会有任何影响。

    为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-localstatic对象。

    static的生命周期
    从被构造开始,直到程序结束为止。函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象称为non-local static对象。
    程序结束时static对象会被自动销毁,也就是它们的析构函数会在main()结束时被自动调用。

    所谓编译单元(translation unit)是指产出单一目标文件(single object file)的那些源码。基本上它是单一源码文件加上其所含入的头文件(#include files)。

    C++对“定义于不同的编译单元内的non-local static对象”的初始化相对次序并无明确定义。这是有原因的:决定它们的初始化次序相当困难,非常困难,根本无解。
    幸运的是一个小小的设计便可完全消除这个问题。唯一需要做的是:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说,non-local static对象被local static对象替换了。Design Patterns迷哥迷姊们想必认出来了,这是Singleton模式的一个常见实现手法。

    为避免在对象初始化之前过早地使用它们,你需要做三件事。第一,手工初始化内置型non-member对象。第二,使用成员初值列(memberinitialization lists)对付对象的所有成分。最后,在“初始化次序不确定性”(这对不同编译单元所定义的non-local static对象是一种折磨)氛围下加强你的设计。

    了解C++默默编写并调用哪些函数

    如果你声明一个类却什么都没做,C++会默默帮你生成如下内容:
    默认构造函数,拷贝构造函数,复制构造函数,赋值运算符,析构函数。
    所有这些函数都是public且是inline的。

    如果成员函数被声明为引用类型或const类型,而类内又没有声明赋值运算符,
    那么编译器将拒绝编译,除非你定义了赋值运算符。

    class temp
    {
    public:
          ....
    private:
          int& Num;
          const string Name;
    };
    

    若不想使用编译器自动生成的函数,就该明确拒绝

    如果不想让类拥有复制构造函数和赋值运算符,可以将复制构造函数和赋值运算符声明为private,并不去实现他们。
    更好的做法是,制作一个Uncopyable基类,使用时直接继承基类就可以:

    class Uncopyable{
    protected:
          Uncopyable(){} //允许继承类构造和析构
          ~Uncopyable(){}
    private:
          Uncopyable(const Uncopyable&)//阻止复制构造函数
          Uncopyable& operator=(const Uncopyable &);
    };
    

    使用时:

    class HomeForSale:Uncopyable{
    
    };
    

    继承类构造函数
    写子类的构造函数时,必须要看看父类的构造函数能否被自动调用;若不能被自动调用,应该显示调用。

    class A
    {
        int a;
    public:
        A(int a)
        {
            this->a = a;
        }
    };
    
    class B:public A
    {
        int b;
    public:
        B(int b):A(10)
        {
            this->b = b;
        }
    };
    

    为多态基类声明virtual析构函数

    作为基类的class一定要有虚析构函数,这样在销毁基类的时候会继续调用继承类的虚构函数。
    不作为基类的class一定不要带有虚析构函数,因为这样会使对象的大小多出50%~100%,因为会多出一个vptr

    欲实现出virtual 函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数该被调用。这份信息通常是由一个所谓vptr(virtual table pointer)指针指出。vptr指向一个由函数指针构成的数组,称为vtbl(virtual table);每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl——编译器在其中寻找适当的函数指针。

    注意:
    STL容器都不带虚析构函数的,因此不适合做基类。
    比如以下这样会导致继承类指针没有被释放:

    class SpecialStirng:std::String{
    };
    SpecialString* pss=new SpecialString("abc");
    string* ps;
    ps=pss;
    delete ps;//此时pss没有被释放,造成内存泄漏
    

    条款08:别让异常逃离析构函数

    析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序

    C++允许在析构函数中抛出异常,但不建议这样做,因为STL容器内包含的对象,如果每个对象都会抛出异常,在同时出现两个异常的情况时,则会导致unexception 行为。
    如果确实在析构函数中发生了异常,导致程序不能再继续运行,可以采取两种方式:

    如果close抛出异常就结束程序。通常通过调用abort完成

    DBConn::~DBConn(){
          try{
                db.close();
          }
          Cath(){
                std::abort();//如果close()发生异常,调用abort终止程序,并记录
          }
    }
    

    吞下因调用close而发生的异常

    DBConn::~DBConn(){
          try{
                db.close();
          }
          Cath(){
          }
    }
    

    如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么 class 应该提供一个普通函数(而非在析构函数中)执行该操作

    应该提供一个函数供客户处理异常:

    class DBConn{
    public:
          close(){  //供客户使用的新函数
          db.clsose();
          closed=true;
          }
    ~DBConn(){
                if(!closed){    //关闭连接,如果客户没有调用close
                      try{db.closed();}
                      catch(){};                  
                }
          }
    private:
          DBConnection db.close();
          bool closed;    
    };
    

    条款09:绝不在构造和析构过程中调用virtual函数

  • 相关阅读:
    标签,css,排版
    浏览器的内核
    焦点事件
    cookie
    浏览器的行为
    百叶窗分析
    水仙花数
    递归函数
    拖拽的问题解决
    正则的具体
  • 原文地址:https://www.cnblogs.com/chendeqiang/p/14194817.html
Copyright © 2011-2022 走看看