天天看点

Effective C++笔记 —— 第五章

  1. 转型

    1.1 const_cast 通常被用来将对象的常量性转除,它也是唯一具有此能力的C++转型操作符

    1.2 dynamic_cast 主要用来执行“安全向下转型”(父类转为子类),也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作

    1.3 reinterpret_cast 意图执行低级转型,实际动作及结果可能取决于编译器,这也就表示它不可移植。例如将一个pointer to inta 转型为一个int。这一类转型在低级代码以外很少见

    1.4 static_cast 用来强迫隐式转换,例如将non-const 对象转为const 对象,或将int转为double等等,但是无法将const转为non-const,这个是有const_cast才办的到

  2. Derived d;

    base* pb=&d;

    有时候上述两个指针并不相同,这种情况下会有一个偏移量在运行期被施行于Derived* 指针上,用以取得正确的base* 指针值

    所以单一的对象可能拥有一个以上的地址(例如以base指向它时的地址和以derived指向它时的地址)

    对象的 布局方式和它们的地址计算方式随编译器的不同而不同,那意味着“由于知道对象如何布局”而设计的转型,在某一平台行得通,在其他平台并不一定行得通

  3. 之所以需要dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但你的手上却只有一个指向base的指针或引用,你只能靠他们来处理对象。一般都两个做法可以避免这种做法,第一,使用容器并在其中存储直接指向derived class对象的指针;第二,可通过base class接口处理“所有可能之各种window派生类”,那就是在base class内提供virtual 函数做你想对各个window派生类做的事,举个例子,虽然只有specialwindow可以闪烁,但是将闪烁函数声明于base class内并提供一份什么也没做的缺省实现代码是有意义的
  4. 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast;如果转型是必要的,试着将它隐藏在某个函数背后,客户随后可以调用该函数,而不需要将转型放进他们自己的代码内;宁可用新式转型,不要使用旧式
  5. 避免返回handles(包括references、指针、迭代器)指向对象内部
    class Point{
    	void setX(int newVal);
    	void setY(int newVal);
    };
    struct RectData{
    	Point ulhc;
    	Point urhc;
    };
    class Rectangle{
    	shared_ptr<RectData> pData;
    	Ponit& upperLeft() const {return  pData->ulhc;}
    };
               
    这个设计是矛盾的,一方面upperLeft被声明为const成员函数,另一方面却返回reference指向private内部数据,调用者于是可以通过这些reference更改内部数据
    Point coord1(0,0);
    Point coord2(100,100);
    const Rectangle res(cood1,cood2);
    rec.upperLeft().extX(50);
               
    降低了封装性,可以做如下更改
    class Rectangle{
    	shared_ptr<RectData> pData;
    	const Ponit& upperLeft() const {return  pData->ulhc;}
    };
               
    有了这样的改变,客户可以读取矩形的Points,但是不能涂写他们。但即便如此,upperLeft还是返回了“代表对象内部的”handles,有可能导致空悬的handles:这种handles所指的东西不复存在
    class GUIObject {...};
    const Rectangle boundingBox(const GUIObject& obj);  //以by value返回一个矩形
    };
               
    现在,客户有可能这么使用这个函数
    GUIObject* pgo;
    const Point* pUpperLeft=&(boundingBox(*pgo).upperLeft());
               
    对boundingBox的调用获得一个新的、暂时的Rectangle对象,这个对象没有名称,所以我们权且称它为temp。随后upperLeft作用于temp身上,返回一个reference指向temp的一个内部成分,更具体底说是指向一个用以标示temp的Points。于是pUpperLeft指向那个Point对象。目前一切还好,但是在那个语句结束之后,temp将被销毁,temp内的Points将被析构,最终导致pUpperLeft指向一个不存在的对象
  6. inline造成的代码膨胀可能导致额外的换页行为,降低指令高速缓存装置的击中率。inline函数通常一定被置于头文件中,因为大多数建置环境在编译过程中进行inline,而为了将一个函数调用转换为被调用函数的本体,编译器必须知道那个函数长什么样子。inline在大多数C++程序中是编译期行为。
  7. templates通常也被置于头文件内,因为它一旦被使用,编译器为了将它具现化,需要知道它长什么样子
  8. virtual函数不能是inline,因为virtual 函数在执行期才知道调用哪个函数,inline在执行前就将调用动作替换为被调用函数的本体