【EffectC++】22、23、24

Item22:将成员变量声明为private

成员变量应该是private的

为什么不采用public成员变量?

  1. 一致性。所有的成员变量都提供访问接口,一个类对外提供的形式就只有函数形式
  2. 使用函数可以让你对成员变量处理有更加精确的控制。可读、可写、读写等
  3. 封装性。当我们有了这个类更好的实现时,而且成员变量都是private的,我们就可以在不破坏客户代码的前提下更换为更好的实现;成员变量在被读写时轻松的通知其他对象;可以验证class的约束条件;可以在多线程中提供同步控制(加锁)等

某种东西的封装性与“当其内容改变时可能造成的代码破坏量”呈反比

protected变量的封装性是不是要比public的要好呢?答案是不一定。当public成员变量被修改,所有使用了它的客户代码破坏;当protected成员变量被修改,所有使用了它的derived class代码被破坏,而后进一步延伸

成员变量只有两种权限:private(封装)和非private(不封装)

总结:

  • 切记将成员变量声明为private。这可以提供客户访问数据的一致性、可细微划分访问控制、允许约束条件获得保证、提供class作者以充分的弹性实现
  • protected并不比public更具有封装性

Item23:宁以non-member、non-friend函数替换member函数

//一个class用来表示网页浏览器 有的函数清理缓存 有的清理记录 有的清理cookie
class WebBrowser{
public:
    ...
    void clearCache();
    void clearHistory();
    void removeCookie();
};

//很多用户想要一整个执行上述操作 两种方法member、non-member
class WebBrowser{
public:
    ...
    void clearCache();
    void clearHistory();
    void removeCookie();
    void clearEverything();
};

void clearEverything(WebBrowser &wb){
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookie();
}
//哪种方法好呢?

non-member、non-friend函数更好,member函数带来的封装性要比non-member、non-friend函数低:

  1. 封装性:愈少的人看到他我们就有愈大的弹性去变化它,这是我们推崇封装性的原因,它使我们能够改变事物而只影响有限客户。考虑对象内的数据,越多的函数能够访问到它,说明它的封装性越差。增加non-member、non-friend函数不会降低对象内数据的封装性!!

    1. 这个论述只适用于non-member且non-friend函数 member和friend函数对class的private成员访问的权力是一样的
    2. non-member、non-friend函数可以变成另一个类的member函数
  2. 编译相依

    C++的做法是让class WebBrowser和non-member函数位于同一个namespace中

    namespace WebBrowserStuff{
    class WebBrowser{
    public:
        ...
        void clearCache();
        void clearHistory();
        void removeCookie();
    };
    
    void clearEverything(WebBrowser &wb){
        wb.clearCache();
        wb.clearHistory();
        wb.removeCookie();
    }
    }
    

    namespace和class不同,namespace可以跨文件而class不能

    我们的namespace std不只有一个头文件<C++ standard library>,而是一共有几十个头文件,因为我们想用到vector就不需要queue等库,这样降低了编译相依

    一个WebBrowser这样的class可能有大量的便利函数clearEverything,有些与书签相关、有些与cookie相关等,一个只与书签相关的便利函数不需要与cookie相关的便利函数发生编译相依。分离他们的直接做法就是放在不同的头文件中

    //头文件webbrowser.h
    namespace WebBrowser{
    class WebBrowser{
    public:
        ...
        void clearCache();
        void clearHistory();
        void removeCookie();
    };	
    }
    //头文件webbrowserbookmarks.h 书签相关的便利函数
    namespace WebBrowser{
    
    }
    //头文件webbrowsercookies.h cookies相关的便利函数
    namespace WebBrowser{
    
    }
    //把不同的non-member便利函数放在不同的头文件中可以降低编译相依
    
  3. 机能扩充性

    这种方法切割不能用于class中的member函数,因为class是一个整体,不能被切割放在不同的头文件

    将所有便利函数放在同一个命名空间中,意味着客户可以轻松扩展这一组便利函数,他们要做的就是在同一命名空间下建一个不同的头文件。比如WebBrowser中放一个下载相关的便利函数,只需要在WebBrowser的命名空间中新建一个下载相关的头文件。这样做对于class是不行的,因为class对于客户是不能修改的!!

总结:

  • 宁可拿non-member non-friend函数代替member函数。这样做可以增加封装性、包裹弹性和机能扩充性

Item24:若所有参数皆需要类型转换,请为此采用non-member函数

当我们想要设计一个class表示有理数, 显然ints转化为这个有理数的隐性转换是合理的,所以构造函数是non-explicit的

//一个表现有理数的class
class Rational{
private:
    int n, d;//n分子 d分母
public:
    Rational(int numerator = 0, int denominator = 1) : n(numerator), d(denominator){}
    int n() const;
    int d() const;
};

现在我们想要实现operator*的方法,应该是member函数还是non-member呢?首先我们假定为member函数

//一个表现有理数的class
class Rational{
private:
    int n, d;//n分子 d分母
public:
    Rational(int numerator = 0, int denominator = 1) : n(numerator), d(denominator){}
    int n() const;
    int d() const;
    const Rational operator*(const Rational &rhs) const;
};
Rational oneEight(1, 8);
Rational oneHalf(1, 2);
Rational result = oneEight * oneHalf;
result = result * oneEight;//正确

result = oneHalf * 2;//正确
result = 2 * oneHalf;//错误

上述语句等同于

result = oneHalf.operator*(2);//正确
//实际执行
//const Rational temp(2);	//隐式转换
//result = oneHalf * temp;
result = 2.operator*(oneHalf);//错误

结论:只有当参数被列于参数列中,这个参数才是隐式转换类型的合格参与者 ,被调用的成员函数所隶属的那个对象(this对象)绝不是隐式类型转换的合格参与者

所以我们应该用non-member函数来写operator*

//一个表现有理数的class
class Rational{
private:
    int n, d;//n分子 d分母
public:
    Rational(int numerator = 0, int denominator = 1) : n(numerator), d(denominator){}
    int n() const;
    int d() const;
};

const Rational operator*(const Rational&lhs, const Rational &rhs);

Rational oneEight(1, 8);
Rational oneHalf(1, 2);
Rational result = oneEight * oneHalf;
result = result * oneEight;//正确

result = oneHalf * 2;//正确
result = 2 * oneHalf;//正确

那么这个operator*需要是friend函数吗?本例不需要,因为它用的是Rational的public接口

所以一定记住member对立面是non-member,而不是freind,不要想着某个与类相关的函数不是member的,就应该是freind的!!!无论何时如果你能避免friend函数就应该避免,因为现实中朋友带来的便利往往少于带来的麻烦

当从objectivec++迈向templatec++时,这个条款就不一定适用了

总结:

  • 如果你需要为某个函数的所有参数(包括this指向的隐喻参数)进行类型转换,那么这个函数必须是non-member的
上一篇:CUMCM→MCM/ICM→NPMCM:关于国赛(全国大学生、研究生、博士研究生数学建模竞赛)和美赛中的数学的专业词汇详细攻略—美国数学建模竞赛


下一篇:R语言non-zero exit status处理:非零状态