Effective C++第四章-设计与声明-1

来源:互联网 发布:企业网络搭建方案文档 编辑:程序博客网 时间:2024/05/17 07:20

多态类型

多态类型在作为接口时有数据保护和隐藏的效果。

多态分为两种:通用多态和特定多态

  1. 通用多态:对工作的类型不加限制,允许对不同类型的值执行相同的代码

    又分为参数多态(parametric)和包含多态(inclusion)

    • 参数多态:采用参数化模板,通过给出不同的类型参数,使得一个结构有多种类型。例如,模板类。

    • 包含多态:同样的操作可用于一个类型及其子类型(注意是子类型,不是子类)。包含多态一般需要进行运行时的类型检查。例如,虚函数“virtual—override”机制。

  2. 特定多态:只对有限数量的类型有效,而且对不同类型的值可能要执行不同的代码

    又分为过载多态(overloading)和强制多态(coercion)

    • 强制多态:编译程序通过语义操作,把操作对象的类型强行加以变换,以符合函数或操作符的要求。程序设计语言中基本类型的大多数操作符,在发生不同类型的数据进行混合运算时,编译程序一般都会进行强制多态。程序员也可以显示地进行强制多态的操作(Casting)。举个例子,比如,int+double,编译系统一般会把int转换为double,然后执行double+double运算,这个int->double的转换,就实现了强制多态,即可是隐式的,也可显式转换。
    • 过载(overloading)多态:同一个名(操作符﹑函数名)在不同的上下文中有不同的类型。程序设计语言中基本类型的大多数操作符都是过载多态的。通俗的讲法,就是c++中的函数重载

    PS:类(class)和类型(type)的区别

    type包括两种:

    1. 基本类型

      int、char、double、bool、unsigned等等

    2. 复合类型
      class、struct、function、array数组、reference引用、union联合体、enum枚举类型。他们基本上都是一个type里面有很多其他的type。

      所以class是类型type的一种,type是一个抽象的概念。

cross-DLL problem

  • 这个问题发生于“对象在一个DLL(动态链接程序库)中被new创建,却在另一个DLL内被delete销毁”。在许多平台上,这一类“跨DLL之new/delete成对运用”会导致运行期错误。
  • tr1::shared_ptr不会有该问题,因为它缺省的删除器是来自“tr1::shared_ptr诞生所在的那个DLL”的delete。例如:
//Stock类派生自Investment类std::tr1::shared_ptr<Investment> CreateInvestment(){    return std::tr1::shared_ptr<Investment>(new Stock);}//返回的那个tr1::shared_ptr可被传递给任何其它DLLs,无需在意“cross_DLL problem”。这个指向Stock的tr1::shared_ptrs会追踪记录“当Stock的引用次数变为0时该调用那个DLL's delete”
  • 最常见的tr1::shared_ptr实现品来自Boost。

    Boost的shared_ptr是原始指针的两倍,以动态分配内存作为薄记用途和“删除器之专属数据”,以virtual形式调用删除器,并在多线程程序修改引用次数时蒙受线程同步化的额外开销。(只要定义一个预处理器符号就可以关闭多线程支持)。总之,它比原始指针大且慢,而且使用辅助动态内存。在许多应用程序中这些额外的执行成本并不显著,然而其降低客户错误的成效却是每个人都看得到。

导入新类型来预防“接口被误用”

举例:

class Data{public:    Data(int month,int day,int year);    ...};...Data d(30,3,1994);//wrong以错误的次序传递参数Data d(2,30,1994);//wrong传递参数不符合要求

导入新类型:

struct day{ explicit Day(int d):val(d){} ... int val;};struct month{explicit month(int m):val(m){}...int val;};struct year{explicit year(int y):val(y){}...int val;};class Data{public:    Data(const month& m,const day& d,const year& y);    ...};//调用Data d(30,3,1995);//wrongData d(day(30),month(3),year(1995));//wrongData d(month(3),day(30),year(1995));//right

也可以在新类型中限制其值:

class month{public:    static month Jan(){return month(1);}    static month Feb(){return month(2);}    ...private:    explicit month(int m);    ...};Data d(month::mar(),day(30),year(1993));

定义一个新的type需要考虑的问题:

(定义一个新的class也就是定义一个新的type)

  1. 新type的对象应该如何被创建和销毁?

  2. 对象的初始化和对象的赋值该有什么样的差别?

    构造函数和赋值操作符的差异

  3. 新type的对象如果被passed by value(以值传递),意味着什么?

    copy构造函数用来定义一个type的passed by value该如何实现

  4. 新type的“合法值”

  5. 新type需要配合某个继承图系吗?

    如果你继承自某些既有的classes,就受到那些classes的设计的束缚,特别是受到其函数是virtual或non-virtual的影响;

    如果你允许其他classes继承你的class,那会影响你所声明的函数-尤其是析构函数是否为virtual。

  6. 新type需要什么样的转换?

    如类型转换函数、可被单一实参调用的构造函数

  7. 什么样的标准函数应该驳回?(声明为private者)

  8. 谁该取用新type的成员?

    帮助你决定哪个成员为public,哪个为protected,哪个为private

  9. 什么是新type的“未声明接口”?

  10. 新的type有多么一般化?

    如果你定义的是一整个types家族,则应该定义一个新的class template。

by reference-to-const代替by value

  1. “昂贵”的by value

    缺省情况下C++以by value方式传递对象至函数。函数参数都是以实际实参的复件为初值,而调用端所获得的也是函数返回值的一个复件。这些复件由对象的copy构造函数产生。

    昂贵的例子:

    class Person{public:Person();private:std::string name;std::string address;};class Student:public Person{public:Student();~Student();private:std::string schoolname;std::string schooladdress;};bool validatestudent(Student s);//声明...Student pla;bool plaIsOk = validatestudent(pla);//调用函数//该例子中的by value方法传递一个Student对象会导致调用一次Student copy构造函数、一次Person copy构造函数、四次string copy构造函数。当函数中Student复件被销毁,会调用六次对应的析构函数.

    一般而言,可以合理假设by value不昂贵的唯一对象就是内置类型和STL的迭代器和函数对象。

  2. 以by reference方式传递参数也可以避免对象切割问题:当一个derived class对象以by value方式传递并被视为一个base class 对象,base class的copy构造函数会被调用。而“造成此对象的行为像个derived class对象”的那些特化性质全被切割掉了,仅仅留下一个base class对象。

    例:

    class window{public:...std::string name() const;//返回窗口名称virtual void display() const;//显示窗口};class windowwithscroll:public window{public:...virtual void display() const;};

    现在有一个函数:

    void printNameanddisplay(window w)//参数可能被切割:参数w被构造成一个window对象。传入的wws是windowwithscroll对象。{ std::cout<<w.name();//调用的是window类型的name函数 w.display();//调用的是window类的display函数}//当调用上述函数windowwithscroll wws;printNameanddisplay(wws);

    解决对象切割的方法:以by reference方式传递参数

    void printNameanddisplay(const window& w){   std::cout<<w.name();w.display();}

返回reference可能出现的错误

任何时候看到一个reference,一定是指向某个既有的对象。

例:

class rational{public:    rational(int numerator = 0 , int denumerator = 1);    ...private:    int n,d;//分子:numerator,分母:denumerator    const rational operator*(const rational& le,const rational& ri);//以by value方法返回的是一个对象};
  1. 一个“必须返回新对象”的函数的正确写法

    inline const rational operator* (const rational& le,const rational& ri){rational rational(le.n * ri.n , le.d * ri.d);}
  2. 函数创建新对象并返回reference的错误写法:

    1. 在stack空间(local变量是在stack空间创建对象)

      const rational& operator*(const rational& le,const rational& ri){rational result(le.n * ri.n , le.d * ri.d);return result;}//local变量result会在函数退出前被销毁;

      任何函数如果返回一个reference指向某个local对象,都是错误的。

    2. 在heap空间(new创建)

      const rational& operator*(const rational& le,const rational& ri){    rational *result = new rational(le.n * ri.n , le.d * ri.d);    return result;}//问题在于谁该对new出来的对象实施delete?
    3. 静态存储区(定义函数内部的static对象)

      const rational& operator*(const rational& le,const rational& ri){static rational result;result = ...;return result;}
      bool operator==(const rational& le,const rational& ri);...rational a,b,c,d;if((a*b)==(c*d)){}//此时(a*b)==(c*d)永远为TRUE