zoukankan      html  css  js  c++  java
  • 四、设计和声明--条款21-23

    条款21:必须返回对象时,别妄想返回其reference

    看下面这个类,是一个表现分数相乘的class:

    class Rational
    {
    public:
        Rational(int numerator = 0, int denominator = 1);
        const Rational operator*(const Rational& lhs, const Rational& rhs);
        ...
    private:
        int numerator;
        int denominator;
    };
    

    这段代码里面,重载乘号返回的是以by-value形式的计算结果。那就要承担额外的构造和析构成本。

    Q:这样做是不建议的吗?

    既然要承担额外的构造和析构成本,那么我们暂且使用引用代替之,看看是否会更好。

    仿佛听起来是正确的,其实不然。任何时候我们使用引用,都要问问,引用绑定的另一个名称是什么?

    方案一

    我们要先定义个变量来被引用绑定:

    const Rational& operator*(const Rational& lhs,const Rational& rhs)
    {
        Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
        return result;
    }
    
    方案一的错误之处
    1. 在这个函数中,我们还是没有避免拷贝构造函数的调用。即使放回了引用,我们还是需要定义个变量来和引用绑定。=
    2. 这个函数有个致命的错误:返回了一个local对象作为引用,出了作用域,local对象就被销毁了。 任何对象和这个local对象返回值绑定,只要对这个返回值做出一点运用,就会立刻坠入“无定义的行为”之中。
    方案二

    避免返回一个local对象。

    const Rational& operator*(const Rational& lhs,const Rational& rhs)
    {
        Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
        return result;
    }
    

    现在我们返回的是一个动态分配的对象,就不会出了这个作用域就被销毁,那么问题来了:谁应该负责对这个对象进行销毁呢?

    可能你真的比较细心,有意识的去释放自己使用完的引用对象,但是如果是这样呢?

    Rational w, x, y, z;
    w = x * y * z;
    

    当执行y*z时就new出一个对象,使用它返回的引用结果再和x相乘,这时已经有两个对象被new出来,但是并没有一个合理的方法去释放它们!

    综合以上的情况,返回一个引用在这种情况下是不妥的,我们宁愿选择承担额外的构造和析构成本的by-value方法。

    作者总结

    绝不要返回pointer或reference指向一个local stack对象,或者返回一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。

    条款22:将成员变量声明为private.

    作者在书中总结了一些优点:

    • 使用函数访问成员变量,函数都有个小括号,方便记忆。
    • 可以精准控制每个成员变量,不至于每个客户都可以用各种方法访问(读、写、读写)。
    • 封装。为所有可能的实现提供弹性。通过调用函数而隐藏其内部实现。
    • 代码破坏量
      • 假如使用public:没有封装性。假如我们取消了一个public成员变量,那么所有使用它的客户端都会被破坏。
      • 家兔使用protected: 没有封装性。如果取消了一个protected成员变量,那么所有使用它的derive对象都会受到破坏。

    综上,其实只有两种访问权限:private(提供封装)和其他(不提供封装)

    总结

    切记将成员变量声明为private。这可赋予客户访问一切数据一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者充分的实现弹性。

    protected并不比public更具封装性。

    条款23:宁以non-member,non-friend替换member函数

    Q:这样做有什么好处?

    封装。首先从封装开始考虑。愈多东西被封装,愈少人可以看到它。俞少人看到,我们就有俞大的弹性去改变它。俞少的代码可以看到数据,封装性就俞强

    所以为了更大的封装性,我们有时候宁以non-member,non-friend函数替换类的member函数。下面给出一个浏览器清除数据的例子:

    我们有一个WebBrowser类,里面有清理高速缓存区,清理历史记录,清理系统中所有的cokies:

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

    现在我们想要有一个能够一次删除全部的函数clearBrowser。这个情况比较自然的做法是让clearBrowser成为一个non-member函数,并且处于和WebBrowser同一个命名空间下:

    namespace WebBrowserStuff
    {
        class WebBrowser{...};  // 包括三个清除函数
        void clearBrowser(WebBrowser &wb);
    }
    

    这样就和我们所讨论的增加封装性的方法一致了。我们将这样non-member函数称为便利函数(为类提供便利执行方法,却不需要访问类中的数据)

    存在大量的便利函数带来的影响和解决方法

    倘若我们存在许多的便利函数,但特定的便利函数只与特定的操作相关:比如某些与书签有关,某些与打印有关,某些与cookie相关,但是如果声明在同一个头文件中,这些便利函数都会有编译相依的关系。事实上,有关书签的便利函数和cookie的便利函数是没有任何关系的。

    解决之道:分别建立多个头文件,需要的时候在引用相应的头文件,这样就能使我们所需要调用的那一部分系统形成编译相依,降低了文件编译依存性。

    千万别将class成员函数用此方法进行切割,成员函数需要保持整体定义,不可被分割成多个部分。

    在我们这一个条款的例子里可以分割成以下样子:

    // webbrowser.h头文件,不可分割。
    namespace WebBrowser
    {
    class WebBrowser
    {
        ...// 将member函数写进去
    }
    }
    

    与书签相关的便利函数独立抽出来放到webbrowserbookmarks.h中:

    namespace WebBrowser
    {
        ... // 与书签相关的便利函数,非member函数。
    }
    

    与cookie相关的便利函数独立抽出来放到webbrowsercookies.h中:

    namespace WebBrowser
    {
        ... // 与cookies相关的便利函数,非member函数。
    }
    

    其他便利函数也均采用此方法即可。

    作者总结

    尽量以non-member函数替换member,friend函数,这样做可以增强封装性,包裹弹性和机能扩充性。

  • 相关阅读:
    python模块--time模块
    python模块--如何相互调用自己写的模块
    Animating Views Using Scenes and Transitions
    fragment 切换
    android textview 设置text 字体
    android intent 5.1
    android EditView ime
    animation of android (4)
    animation of android (3)
    animation of android (2)
  • 原文地址:https://www.cnblogs.com/love-jelly-pig/p/9669332.html
Copyright © 2011-2022 走看看