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

来源:互联网 发布:苹果淘宝下载 编辑:程序博客网 时间:2024/05/03 08:12

接口函数应该为public而数据一般不应为public

  • 如果没有继承,类只有两种用户:类本身的成员和该类的用户。用户只能访问public接口,类成员和友元既可以访问public成员也可以访问private成员。

    有了继承,就有了类的第三种用户:从类派生定义新类的程序员。派生类不能访问基类的private,可以访问基类的protect。

    从封装角度看,其实只有两种访问权限:private(提供封装)和其他(不提供封装)。protect成员变量和public成员变量一样缺乏封装性。

  • 关于友元(friend):友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。只能出现在类定义的内部。通常将友元声明成组的放在类定义的开始或结尾。

    友元可以是:

    1. 普通的非成员函数
    2. 已定义的其他类的成员函数(将成员函数声明为友元时,函数名必须用该函数所属的类名字加以限定)
    3. 整个类(该类的所有成员函数都可以访问授予友元关系的那个类的非公有成员)
  • 使用函数可以让你对成员变量的处理有更精确的控制,也就是将成员变量隐藏在函数接口的背后。

宁以non-number、non-friend替换member函数

例:

class webBrowser{public:    ...    void clearCache();    void clearHistory();    void removeCookies();    ...};

member函数实现:

class webBrowser{public:    void clearEverything();//调用clearCache+clearHistory+removeCookies    ...};

non-member函数实现:

void clearBrowser(webBrowser& wb){    wb.clearCache();    wb.clearHistory();    wb.removeCookies();}

计算能够访问该数据的函数数量,作为一种粗糙的量测。愈多函数可访问它,数据的封装性就愈低。

friend函数对class private成员的访问权利和member函数相同。

如果要你在一个member函数和一个non-member,non-friend函数之间做抉择,而且两者提供相同机能,那么,导致较大封装性的是non-member non-friend函数。因为non-member non-friend函数并不增加“能够访问class内之private成分”的函数数量。non-member non-friend函数也可称为便利函数

为了封装性而让函数“成为class的non-member”,并不意味它“不可以是另一个class的member”。

在C++,比较自然的做法是让non-member non-friend函数位于该类所在的同一个namespace内

为了将便利函数进行分类,可以将所有便利函数分组放在多个头文件内但隶属同一个命名空间,意味着客户可以轻松扩展这一组便利函数。需要做的就是添加更多non-member non-friend函数到此命名空间内。例如:

//头文件"webbrowser.h"该头文件针对class webBrowser自身和webBrowser的核心机能(几乎所有客户都需要的non-member函数)namespace webBrowserstuff{class webBrowser{...};void clearBrowser(webBrowser& wb);...}//头文件“webbrowser_1.h”namespace webBrowserstuff{  ...//一类接口:例如与网页书签相关的便利函数}//头文件“webbrowser_2.h”namespace webBrowserstuff{  ...//另一类接口:例如与cookie相关的便利函数}//这段代码也显示了命名空间的特征:1、命名空间的不连续性,也就是可以分散在多个文件中2、利用1特征将接口和实现分离

PS:关于命名空间的博客链接

若需要为某个函数的所有参数进行类型转换,请为此采用non-member函数

class Rational{public:    Rational(int numerator = 0, int denomerator = 1);//构造函数刻意不为explicit以允许隐式转换    int numerator() const;    int denumerator() const;private:    ...};
  1. 定义实现混合式算术的成员函数

    class rational{public:...const rational operator*(const ratinal& r)const;//成员函数实现操作符*};

    当代码中进行混合式算术:

    rational one(1,2);//调用1:rational result = one * 2;//right//相当于const rational temp(2);result = one * temp;result = one.operator*(temp) ;//将参数2隐式转换//调用2:rational result = 2 * one;//wrong//相当于result = 2.operator*(one); 整数2没有相应的class,也就没有operator成员函数编译器,此时编译器也会尝试result =   operator*(2,one)的non-member operator*调用...
  2. 让operator*成为non-member函数(允许编译器在每一个实参身上执行隐式类型转换)

    const rational operator*(const rational& l , const rational& r){return rational(l.numerator()*r.numerator() , l.denumerator()*r.denumerator());}

    此时允许编译器在每一个实参身上执行隐式类型转换

    rational one(1,2);rational result = one * 2;//rightrational result = 2 * one;//right

swap函数

相关知识点:

  • 全特化(template<>表示全特化版本)
  • C++的名称查找法则(name lookup rules)

首先,如果swap的缺省实现对程序员的class或class template提供可接受的效率,则使用swap的缺省实现

namespace std{    template<typename T>    void swap(T& a, T& b)    {        T temp(a);        a=b;        b=temp;    }}//只要类型T支持copying(copy构造函数和copy assignment操作符)缺省的swap实现代码就会帮你置换类型为T的对象

其次,如果swap缺省的效率不足,如:

class widgetm{private:    int a, b, c;    std::vector<double> v;    ...//假设有很多成员};class widget{public:    widget(const widget& r);    ...private:    widgetm* widget_p;}//缺省的swap会复制三次widget,还复制三个widgetm,效率不行。其实只需要交换两个widget类的widget_p

此时可以尝试以下方法:

通常我们不能够(不被允许)改变std命名空间内的任何东西,但可以(被允许)为标准templates制造特化版本,使专属于我们自己的classes。

1.提供一个public swap成员函数

让它高效地置换你的类型的两个对象值。这个成员函数绝不该抛出异常。因此当你写一个自定版本的swap,往往提供的不只是高效置换对象值的方法,而且不抛出异常

2.1 std的特化版本

如果正编写一个class(而非class template),为你的class特化std::swap,并令它调用你的swap成员函数

class widget{public:    ...    //提供一个public swap成员函数,让它高效地置换你的类型的两个对象值。这个成员函数绝不该抛出异常    void swap(widget& other)    {            using std::swap;            swap(widget_p , other.widget_p); //按照C++的名称查找法则:编译器选择的顺序是:先std::swap的T专属特化版,如果没有则是std::swap    }};namespace std{    template<>     //template<>表示std::swap的全特化版本    void swap<widget>(widget& a, widget& b){a.swap(b);} //std::swap的特化版本,针对T为widget设计的。即若要置换widget类,调用widget类的swap员函数}

2.2 栖身在某个命名空间的T专属版本

当widget和widgetm是class template而非class

如果对std::swap实现特化版本,代码应如下:

namespace std{    template<typename T>    void swap<widget<T> >(widget<T>& a , widget<T>& b)           {a.swap(b);}//wrong }

C++只允许对class template偏特化,在function template身上偏特化行不通

解决方法:在你的class template所在命名空间内提供一个non-member swap,并令它调用public swap成员函数

namespace widgetstuff{...//此时widget和widgetm是class template而非classtemplate<typename T>class widget{  public:        ...     void swap(widget& other)     {         using std::swap;//利用using使std::swap在函数内曝光         swap(widget_p , other.widget_p); //按照C++的名称查找法则:编译器选择的顺序是:先T专属的swap,如果没有则是std::swap     }  };  ...  template<typename T>  void swap(widget<T>& a , widget<T>& b)  {      a.swap(b);  }}
原创粉丝点击