• 《Effective C++》第4章 设计与声明(2)-读书笔记


    章节回顾:

    《Effective C++》第1章 让自己习惯C++-读书笔记

    《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

    《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

    《Effective C++》第3章 资源管理(1)-读书笔记

    《Effective C++》第3章 资源管理(2)-读书笔记

    《Effective C++》第4章 设计与声明(1)-读书笔记

    《Effective C++》第4章 设计与声明(2)-读书笔记

    《Effective C++》第5章 实现-读书笔记

    《Effective C++》第8章 定制new和delete-读书笔记


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

    首先,有两个小点理由支持你将成员变量声明为private。

    (1)接口的一致性。

    如果public接口的都是函数,那么客户在调用时就不用考虑是否需要加小括号,因为每个调用的都是函数,必须加小括号。

    (2)精细的访问控制。

    使用函数可以让你对成员变量的处理有更精确的控制,如果你令成员变量为public,每个人都可以读写它。

    最重要的是private提供封装性:

    如果你通过函数访问成员变量,后面可以更改某个计算替换这个成员,而class客户一点也不会知道class的内部已经变化了,只需重新编译即可。

    假设你将一个成员变量声明为public或protected而客户开始使用它,就很难改变那个成员变量所涉及的一切。太多代码需要重写、测试、重写文档、编译。从封装的角度,其实只有两种访问权限:private和其他(不提供封装)。

    请记住:

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

    (2)protected并不比public更具封装性


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

    下面有一个class:

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

    用户希望把这三个接口通过提供一个函数去做,可以定义一个成员函数调用这三个函数。

    void WebBrowser::clearEverything()    //成员函数,调用clearCache、clearHistory和removeCookies
    {
        clearCache();
        clearHistory();
        removeCookies();
    }

    当然,这个功能也可以通过一个非成员函数提供:

    void clearBrowser(WebBrowser& wb)        //非成员函数
    {
        wb.clearCache();
        wb.clearHistory();
        wb.removeCookies();
    }

    现在要考量的是哪一种做法比较好?

    面向对象守则要求,数据以及操作数据的函数应该被捆绑在一起,这意味着它建议member是较好的选择。这个建议是错误的,是对面向对象真实意义的一个误解。

    面向对象守则要求数据应该尽可能被封装。与直观相反,clearEverything带来的封装性比clearBrowser要低。因为non-member non-friend函数不能访问class内的private。

    注意:这里考量的是member和non-member non-friend二者提供相同的机能时的一个抉择。friends对private的访问权利与member函数相同。


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

    通常令class支持隐式类型转换是不好的,但也有例外。假设一个class表示有理数,那么允许整数“隐式转换”为有理数是比较合理的。

    class Rational
    {
    public:
        Rational(int numerator = 0, int denominator = 1);
        int numerator() const;            //分子访问函数
        int denominator() const;        //分母访问函数
    };

    你想让这个class支持乘法运算。可能声明operator*为成员函数。

    class Rational
    {
    public:
        ...
        const Rational operator*(const Rational& rhs) const;
    };

    这个设计按照下面这种方式使用是没问题的:

    Rational oneEighth(1, 8);
    Rational oneHalf(1, 2);
    
    Rational result = oneHalf * oneEighth;        //好的,没问题
    result = result * oneEighth;                //好的,没问题

    当你想使用混合运算时:

    result = oneHalf * 2;            //好的,没有问题
    result = 2 * oneHalf;            //错误,不满足交换律

    本质上上面的用法与下面的等价:

    result = oneHalf.operator*(2);        //result = oneHalf * 2;    
    result = 2.operator*(oneHalf);        //result = 2 * oneHalf;

    所以你就明白为什么不满足交换律了。为了支持混合运算,需要将operator*设置为non-member函数。

    const Rational operator*(const Rational& lhs, const Rational& rhs)
    {
        return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
    }

    下面的调用的OK的:

    Rational oneFourth(1, 4);
    Rational result;
    
    result = oneFourth * 2;        //好的,没问题
    result = 2 * oneFourth;        //好的,没问题

    这里还要说明的一点是构造函数一定不能声明为explicit的,否则整型的数值2就不能隐式转换为Rational对象了

    请记住:如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是non-member的。


    条款25:考虑写出一个不抛出异常的swap函数

    swap函数就是将两对象的值彼此赋予对方。缺省情况下,swap可由STL提供的swap算法完成。典型实现如下:

    namespace std 
    {
        template<typename T>
        void swap(T& a, T& b)
        {
            T temp(a);
            a = b;
            b = temp;
        }
    }

    只要T支持拷贝(通过拷贝构造函数和copy assignment操作符完成),就可以利用该算法。

    但存在某些情况,缺省的swap行为往往效率较低。例如,以指针指向一个对象。考虑下面的class:

    class WidgetImpl
    {
    public:
    ...
    private:
        int a, b, c;
        vector<double> v;
    };
    
    class Widget
    {
    public:
        Widget(const Widget& rhs);
        Widget& operator=(const Widget& rhs)
        {
            ...
            *pImpl = *(rhs.pImpl);
            ...
        }
    private:
        WidgetImpl *pImpl;
    };

    如果我们要交换Widget对象,缺省的算法会拷贝3个Widget对象和3个WidgetImpl对象,效率很低。实际上,我们只需要置换pImpl指针的指向即可。

    为了解决效率问题,我们需要告诉std::swap,当交换Widget对象时,只需要交换指针就好了。可以将std::swap针对Widget特化。

    namespace std 
    {
        template<>
        void swap<Widget>(Widget& a, Widget& b)
        {
            swap(a.pImpl, b.pImpl);            //交换指针值
        }
    }

    这只是个思路,一般我们不能改变std内的任何东西,我们可以令Widget声明一个swap的public成员函数做置换工作,然后将std::swap特化。

    class Widget
    {
    public:
        void swap(Widget& other)
        {
            using std::swap;                //
            swap(pImpl, other.pImpl);
        }
    };

    注意:成员swap函数绝不可抛出异常。因为swap的一个最好应用是帮助class提供强烈的异常安全性保障。

    请记住:当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。


    补充说明:条款25还有一些其他知识,我没有理解的特别好,就没有说明,但整个问题是由swap效率引发的,下次回顾时再补充吧。

  • 相关阅读:
    文件工具类之FileUtils
    JAVA8日期工具类
    mybatis模糊查询匹配汉字查询不出来数据,匹配字符和数字却可以的问题解决
    问到ConcurrentHashMap不要再提Segment了
    开发中常用工具
    Spring 如何解决循环依赖?
    JVM8基础概念总结
    String字符串相等判断
    面试再也不怕问到HashMap(二)
    面试再也不怕问到HashMap(一)
  • 原文地址:https://www.cnblogs.com/mengwang024/p/4453217.html
Copyright © 2020-2023  润新知