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函数,这样做可以增强封装性,包裹弹性和机能扩充性。

  • 相关阅读:
    JavaBean的详细及引用
    动态页面,登陆,注册,留言 JSP
    简单登陆,注册的动态网页
    11.24作业3
    11.24作业2
    转: JAVA递归算法实例小结
    转: javascript实现全国城市三级联动菜单代码
    转: 我们为什么使用ORM?
    转:Ajax中的get和post两种请求方式的异同
    转: JSTL SQL标签库 使用
  • 原文地址:https://www.cnblogs.com/love-jelly-pig/p/9669332.html
Copyright © 2011-2022 走看看