美文网首页
Effective C++ 4: Design & Declar

Effective C++ 4: Design & Declar

作者: my_passion | 来源:发表于2021-07-21 23:23 被阅读0次
    软件设计: 一般构想 -> implement + interface

    本章阐述 良好 C++ interface 的 设计和声明

    封装性 / 扩展性 / 一致性 / 高效性

1 interface易使用 / 不易 误用

    1   `防止 interface 误用` 
                
            4 种方法

                (1) `建立 新/wrapper 类型`
                
                        防止参数次序 传错
                                
                                Date(Month(3), Day(30), Year(1995) );
                    
                (2) `限制 对象有效值: 预先定义 所有有效值`
                        
                        Date d(Month::Jan(), Day(30), Year(2000) );
                    

                (3) `限制 类型上的操作`

                    `1) const`
                    
                        乘 运算符 operator* 的 return type 用 const 限制
                            可 防止 a*b = c 的 用户无意错误

                        if( a*b = c ) // 原意 是 ==, 结果不小心写错了

                (4) `不要让 client 直接管理资源`

                        `任何接口, 若 要求 client 必须记得某些事情, 就有着 不正确使用 的 倾向`

                            Factory Method 返回 new 对象 的 raw pointer

                                R* createR();

                            为避免 资源泄露, 返回的 `raw pointer 最终必须被删除`

                                => 至少开启 2 个错误

                                    [1] 没 delete ptr

                                    [2] delete ptr 超过 1 次
                                    
                                        |
                                        |   解决 
                                        |/
                            `返回 的 raw pointer 存到 smart pointer, 将 delete 的 责任 推给 SP`

                                        |   
                                        |   更好的设计: 先发制人
                                        |/
                        `令 Factory function 返回 SP -> 强迫 client 将 return value 存到 SP`

                            std::shared_ptr<R> createR();
                            
    2   促进 interface 正确使用

        (1) `接口一致性`
        
                C++ 
                    STL容器: 均用 size()
                    
        (2) `与 build-in 类型 行为 兼容`
        
    3    shared_ptr 定制 deleter`

        (1) RC == 0 时, 自动调用 deleter`

            std::shared_ptr<R> createR()
            {
                std::shared_ptr<R> spR( static_cast<R*>(0), clearR );
                spR = ... // 指向 目标 object
                return spR;
            }

2 设计 class 就像 设计 type

3 选 pass-by-reference-to-const 还是 pass-by-value

    1   pass by value to/from function 的 代价
            
            arg -> copy ctor / return value -> copy ctor + 相应 dtor
    
    2   等价 但 更高效的做法: pass-by-reference-to-const

        (1) by reference

            [1] `回避 那些 ctor / dtor: 因为 没 新对象 被创建`

            [2] `avoid `对象切割 slice` 问题

                    `Derived 对象 pass by value 到 para 为 Base 的 func -> Derived 对象 的 derived part 被 切除`
                    
        (2) const

            `防止 arg 被修改`
    
    3    pass by value / pass by reference to const 两者如何取舍 ?

            ————————————————————————————
            选 pass by value
                [1] 内置类型
                [2] STL iterator
                [3] function object
            ————————————————————————————
            pass by reference to const 
                else
            ————————————————————————————
            
                Note
                    而不是 根据 type 本身大小

                        许多 对象
                            包括 `大多 STL 容器` — 
                                `内含 的 东西只比 1 个 指针` 多一些,
                                
                                    但 `复制这种对象` 却需承担 `复制 那些 指针 所指的 每一样东西`

4 必须 return object 时, 不要 return reference

    1   `乘运算符` 正常应返回 const type 
            
            欲尝试 返回 ref 
                
                =>  `函数内 应 创建 新对象` 
    
                    3 种方法 及其 问题`

                    [1]     `stack 空间 创建之`

                                local 变量 => 函数 return 后 被销毁 

                                    => ref 指向 曾经的 A 但 已被销毁 的 memory 
                                    => undefined behavior

                    [2]     `heap 空间 创建之`

                                谁负责 delete ?

                                    同一语句 调 2 次 乘: w = x * y * z; // operator*( operator*(x, y), z )
                                    
                                        => 2 次 new 
                                    
                                            => `没法合理 取出` 返回的 `ref 背后 隐藏 的 指针` 

                                                => 没法 合理 delete ptr
                                                
                    [3]     `local static 对象`

                                1] not 多线程 安全

                                2] 深层次 瑕疵

                                    if( (a*b) == (c*d) )
                                        即 if( operator==( operator(a, b), operator(c, d) ) ) 永远为真`


                                        `两次返回 的 ref` 都 指向 `内部定义的 static 对象` 

                                            client 看到的 `永远都是 static 对象` 的 `现值` 
                        |
                        |   解决 
                        |/  
                    `必须返回 新对象 时, 就返回 新对象`
    
    2   conclusion

        ——————————————————————————————————————————————
        绝不要 返回
            ——————————————————————————————————————————
            (1) ptr / ref 指向 的 local stack 对象
            ——————————————————————————————————————————
            (2) ref       指向 的       heap 对象
            ——————————————————————————————————————————
            (3) ptr / ref 指向 的 local static 对象,
                
                    + `可能同时需要 多个这样的对象`
        ——————————————————————————————————————————————

5 mem data 声明为 private

    1   mem data 为什么 不采用 public

            封装
    
    2    mem data 为什么 不采用 protected

            理由 与 public 相近

                从 `封装` 角度看, `只有2种 访问权限: private ( 提供 封装 ) 和 其他 ( 不提供 封装 )`

6 选 non-mem / non-friend 还是 mem func

    1    non-mem func 优于 mem func` 的 2个地方`: `封装性` 更高 & `包裹弹性` 更大
                                                    |               |
                                                    |               |   =>
                                                    |/              |/
                                                与 直观相反      `编译依赖` 更低

        (1) 封装性 
                
                non-mem no-friend func vs. mem func
            
                    ————————————————————————————————————————————————————————————————————————
                    前提: 提供 相同机能 
                                    |
                                    |如 
                                    |/
                            都能访问 public mem func
                        ————————————————————————————————————————————————————————————————————
                        前者  `只能访问 指定 public mem func`
                        ————————————————————————————————————————————————————————————————————
                        后者   还可访问 private data / private function / enum / typedef
                        ————————————————————————————————————————————————————————————————————
                            => 后者 封装性 更低
                    ————————————————————————————————————————————————————————————————————————        
                                
        (2) `分离 编译依赖`
                    |\
                    | 作用 2
                    |           作用 1
                `utility func` - - - -> 提高封装性 
                    |
                    |   指 
                    |/
                non-mem non-friend func

                                    utility func 想访问 的 public mem func 所在 class
                                            |\
                                            |   
                [1] 将 `utility func` 和 `目标 class` 放 同一 utility namespace`
                            + 
                        `namespace 可 跨多个源文件`
                            |
                            |   Note
                            |/
                            class 不能
                        
                        将 `作用不同` 的各 `utility functions` 
                            
                            放 `不同 头文件` 但 `隶属 同一 namespce`
                                
                                可实现 `分离 编译依赖关系 => 切割机能`
                        
                                    `不同 clients 只需要 与 其感兴趣的 
                                        utility funcs 编译依赖
                                            #include "some_utility_func.h"
                
                [2] STL 正是用这种思路
                        <vector> <list> ... 声明 std 某机能,
                            若 client 只想用 vector 机能, 
                                只需 #include <vector>
                
                
                    // a.h
                    namespace N
                    {
                        class A { ... };
                        // 核心机能 的 utility func
                    }

                    // a_function1.h
                    namespace N
                    {
                        // 与 功能1 相关 的 utility func
                    }

7 若 所有 参数 ( 包括 this 所指 *this: 如 运算符重载函数 的 lhs 相应实参 ) 均 需 type conversion, 则 用 non-mem func

    `分数 类 ( 分子 / 分母 )` + `乘 运算符`
                                 |
                                 |  lhs + rhs 2 个 para 均 `转换为/取出` 分子 分母   
                                 |/
                            分子分母 分别相乘
    
        ————————————————————————————
        `乘 运算符` 

            选 哪种函数 ?
                ————————————————————
                [1] mem func
                ————————————————————
                [2] non-mem func
                ————————————————————
                [3] 是否 作 friend
        ————————————————————————————
    
    1   用 mem func

        class A
        {
        public:
            A (int numerator = 0;,
                        int denominator = 1); 

            int numerator() const;
            int denominator() const;

            const A operator*(const A& rhs) const;
        };

            `左运算对象 为 数字字面量` 时
                
                `不满足 交换律`
                
                    result = 2 * oneHalf; // error: 2.operator*(oneHalf);

            |
            |   乘 要满足 交换律 
            |   
            |   解决 
            |/  
    2   non-mem func

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

    3   是否作 friend?

            答: 不必要 
                        
                任何时候, 可避免 friend 就 避免

8 不抛出异常swap

    0   swap 应用
        
        ——————————————————————————————————
        [1] `exception-safe 编程 的脊柱`
        ——————————————————————————————————
        [2] 处理 `自我赋值`
        ——————————————————————————————————
            swap: 置换两对象的值

    1   缺省 swap

        std::swap 典型实现: 引用传递 + 3 次 copy 
                                              |
                                              |  要求 
                                              |/
                                        T 支持 copying

                template<typename T>
                    void std::swap(T& a, T& b) { T temp(a); a = b;  b = temp; }
    
            问题: 对 某些 类型, 3 次复制 无一必要
                        |
                        |   如
                        |/
                用 pimp 手法 的 类型

    2   `用 pimp 手法 的 类型 A` 下 针对 `T 为 A` 设计 `std::swap 全特化` 版本

            (1) 缺省 swap 问题 
                
                效率低
                    
                    `不止 copy 3 个 对象, 还 copy 3 个 Impl 对象`
                    
                        A& A::operator=(const A& rhs){
                            // ...
                            *pImpl = *(rhs.pImpl);
                            // ...
                        }                   
                    |
                    |   解决 
                    |/
                  思路
                        `swap 2 个 对象, 只需 swap 其 内部 pImpl 指针`

                            `std::swap 全特化: 目标 类型 T == A`

                                template<>
                                void std::swap<A> (A& a, A& b) { swap(a.pImpl, b.pImpl); }
                    
                    |   问题 
                    |/
                A 对象 直接访问 private 成员 => 编译不过
                    |
                    |   解决
                    |/
            std::swap `全特化 版` 
                        |
                        |   template<>              
                        |   void std::swap<A>(A& a, A& b)
                        |/      
                    调 `A 成员函数 swap`
                            |
                            |   swap(pImpl, rhs.pImpl);
                            |
                            |/
                        调 标准 swap: internal ptr 作 实参                    
                        
                与 STL 容器 做法相同

    3   泛化: A / AImpl 均为 class template

        (1) std 内 偏特化 template func -> 编译报错 
                    
                C++ `只 允许 偏特化 template class`
                C++ `不 允许 偏特化 template func`

                    template<typename T>
                    void std::swap< A<T> >(A<T>& a, A<T>& b)
        
                |
                |   解决 
                |/
        (2) `添加 templete func 重载`

                std 内 添加 template func 重载 -> 编译报错 
                        
                    std 内   `不可 添加 新的 template, 可 全特化 template`
    
                        template<typename T>
                        void std::swap(A<T>& a, A<T>& b)
            
                |   解决 
                |/
        (3) `自定义 namespace` 内 添加 template func 重载 ( swap ) -> 调 `A::swap`

                这种做法 对 class / class template 都 ok

            ———————————————————————————————————————————————————————————————
        (4) `client 调 swap 匹配顺序` 
                ———————————————————————————————————————————————————————————
                [1] ADL 
                    `T 所在 namespace 之 T 专属 swap 
                                                |
                                                |   如: 调 
                                                |/
                                            T 内 成员函数 swap 
                                                |
                                                |   调 
                                                |/
                                            internal ptr 作 实参 调 
                                                经 using 声明 引入的 
                                                    标准 swap // std::swap
                                                
                ———————————————————————————————————————————————————————————
                [2] std::swap 之 全特化版
                ———————————————————————————————————————————————————————————
                [3] std::swap 之 经典版
            ———————————————————————————————————————————————————————————————

            template<typename T>
            void f(T& obj1, T& obj2)
            {
                // [1] 令 std::swap 在 该 scope 内 可用
                using std::swap; 
        
                // [2] ADL: 不必 using T_namespace::swap
                swap(obj1, obj2); // 如 T == A 或 N::A<double> 
            }       |
                    |/
                template<typename T>    
                    void N::swap(A<T>& a, A<T>& b) { a.swap(b); }   
                            |
                            |/
                    template<typename T>
                        N::A::swap(pImpl, rhs.pImpl);
                                |
                                |/
                        using std::swap;
                        
            ————————————————————
            test
            ————————————————————    
                T 为 3 种 类型 
                ————————————————
                    [1] 内置类型 
                    [2] 类 
                    [3] 类模板 
            ————————————————————

    4   mem swap 绝不可抛出异常

        `swap 的 最好应用: 提供 `exception-safety`

        `自定义 swap` 版本 往往同时提供
            
            `高效 swap` 和 exception-safety`
                    |
                    |                       其上 操作 绝不会 抛出异常
                    |                           |\
                    |                           |
                    |/                          |
                `高效 swap` 几乎总是 基于 `内置操作` 
                                                |
                                                |   如 
                                                |/
                                            pImpl 手法 的 internal ptr
=== 详细

1 interface易使用 / 不易 误用

    0   概述

        (1) C++ 的 各种 interface

            function 接口
            class 接口
            template 接口

            接口: client 与  code 交互入口

        (2) 若 client 企图 使用某个接口, 却 没有获得预期结果,
                该 code 就不该 编译通过 -> 责任就在于 coder

    1   `防止 interface 误用` 
            
            4 种方法

                (1) `建立 新/wrapper 类型`
                
                        防止参数次序 传错
                                
                                Date(Month(3), Day(30), Year(1995) );
                                
                                    class Date
                                    {
                                    public:
                                        Date(int month, int day, int year);
                                    };

                                    class Date
                                    {
                                    public:
                                        Date(const Month& m, const Day& d, const Year& y);
                                    };

                (2) `限制 对象有效值: 预先定义 所有有效值`
                        
                        Date d(Month::Jan(), Day(30), Year(2000) );
                        
                            class Month
                            {
                            public:
                                static Month Jan() { return Month(1); }
                                // ...
                                static Month Dec() { return Month(12); }
                            private:
                                explicit Month(int m);
                            };

                (3) `限制 类型上的操作`

                    `1) const`
                    
                        乘 运算符 operator* 的 return type 用 const 限制
                            可 防止 a*b = c 的 用户无意错误

                        if( a*b = c ) // 原意 是 ==, 结果不小心写错了
                        

                    `2) 限制 自定义 type 与 内置类型` 的 `同一操作 表现一致`

                            int 型 *
                                不允许 a*b = c 的 操作

                (4) `不要让 client 直接管理资源`

    2   促进 interface 正确使用

        (1) `接口一致性`
        
                C++ 
                    STL容器: 均用 size()    
                    
            不一致性 对 开发人员造成 心理和精神折磨

                Java 
                    array / string: length()
                    list: size()


        (2) `与 build-in 类型 行为 兼容`

    3   `shared_ptr 定制 deleter`

        (1) RC == 0 时, 自动调用 deleter

        (2) std::shared_ptr `缺省 deleter 来自 源 DLL` -> 防范 `跨 DLL 问题: object 在 某一 DLL(动态链接库) 被 new, 却在 另一 DLL 中 被 delete` 

    4    Conclusion

        shared_ptr 让 接口设计者 阻止了 大群 client 犯下 资源泄露 的 错误

2 设计 class 就像 设计 type

    1   object `如何 创建 & 销毁`
        
        ctor
        dtor

            // 打算定制 内存分配和释放 的话
            operator new/delete []

    2   object `初始化 / 赋值` 差别

    3   object 若被 pass by value, 意味着 什么?`

            copy ctor 定义 type 的 pass by value 如何实现

    4   `有效值`

            决定 
                invariant ( 约束条件 )
                ctor / assignment / setter 函数 必须进行的 错误检查

    5   是否需要 配合某 继承体系

    6   需怎样的 conversion

        (1) T1 -> T2 隐式转换

            1) T1 内:    类型转换运算符 T1::operator T2 ()

            2) T2 内:    non-explicict-one-argument Ctor

        (2) T1 -> T2 显式转换
        
                T1::get() 成员函数

    7   合理的 操作符 和 函数

            声明 哪些 function
            哪些 该为 member func

    8   哪些 compiler 隐含自动生成的 函数 该弃用
            声明为 private

    9   谁该 取用 新 type 成员
    
        mem 该放 public / private / protected ?

        哪个 class / function 该为 friend

    10   type 家族 -> class template

    11  是否真的需要 新 type?

        也许 non-member function 或 template 就能达到目标

3 选 pass-by-reference-to-const 还是 pass-by-value

    1   pass by value to/from function 的 代价
            
            arg -> copy ctor / return value -> copy ctor + 相应 dtor

                Base / Derived 都只有 1 个 std::string 成员数据

                    Derived 以 by value 方式 传给 函数 时, 发生什么?

                        Base / Derived copy ctor
                            + 相应的 string mem 的 copy ctor
                            + 相应 dtor 
                                = 4 次 ctor + 4 次 dtor

    2   等价 但 更高效的做法: pass-by-reference-to-const

        (1) by reference

            [1] `回避 那些 ctor / dtor: 因为 没 新对象 被创建`

            [2] `avoid `对象切割 slice` 问题

                    `Derived 对象 pass by value 到 para 为 Base 的 func -> Derived 对象 的 derived part 被 切除`
                
                class Base
                {
                public:
                    virtual void vf() const;
                };

                class Derived: public Base
                {
                public:
                    virtual void vf() const;
                };

                void f(Base b) { b.vf(); } // Derived 对象 被 切割

                Derived d;
                f(d); // f 内 调 Base::vf()
                    |
                    |   解决 
                    |/
                void f(const Base& rb) {  rb.vf(); }
        
        (2) const

            `防止 arg 被修改`

    3    pass by value / pass by reference to const 两者如何取舍 ?

4 必须 return object 时, 不要 return reference

    1   乘运算符 正常应返回 const type 
            
            欲尝试 返回 ref 
                
                =>  `函数内 应 创建 新对象` 
    
                    3 种方法 及其 问题`

                    [1]     `stack 空间 创建之`

                                local 变量 => 函数 return 后 被销毁 

                                    => ref 指向 曾经的 A 但 已被销毁 的 memory 
                                    => undefined behavior

                                        const A& 
                                        operator* (const A& lhs, const A& rhs)
                                        {
                                            A ret(...);
                                            return ret;
                                        }

                    [2]     `heap 空间 创建之`

                                谁负责 delete ?

                                    同一语句 调 2 次 乘 
                                    
                                        => 2 次 new 
                                    
                                            => `没法合理 取出` 返回的 `ref 背后 隐藏 的 指针` 

                                                => 没法 合理 delete ptr

                                            const A& 
                                            operator* (const A& lhs, const A& rhs)
                                            {
                                                A* ret = new A(...);
                                                return *ret;
                                            }

                                            A w, x, y, z;
                                            w = x * y * z; // operator*( operator*(x, y), z )

                    [3]     `local static 对象`

                                1] not 多线程 安全

                                2] 深层次 瑕疵

                                    if( (a*b) == (c*d) )
                                        即 if( operator==( operator(a, b), operator(c, d) ) ) 永远为真`


                                        `两次返回 的 ref` 都 指向 `内部定义的 static 对象` 

                                            client 看到的 `永远都是 static 对象` 的 `现值` 
                        |
                        |   解决 
                        |/  
                    `必须返回 新对象 时, 就返回 新对象`

                        const A 
                        operator* ( const A& lhs, 
                                    const A& rhs) 
                        {
                            return A(...);
                        }
    
    2   conclusion

        ——————————————————————————————————————————————
        绝不要 返回
            ——————————————————————————————————————————
            (1) ptr / ref 指向 的 local stack 对象
            ——————————————————————————————————————————
            (2) ref       指向 的       heap 对象
            ——————————————————————————————————————————
            (3) ptr / ref 指向 的 local static 对象,
                
                    + `可能同时需要 多个这样的对象`
        ——————————————————————————————————————————————

5 mem data 声明为 private

    1 mem data 为什么 不采用 public

        (1) 语法 `一致性`

            public 接口 内 都是 function, client 访问 mem data 时, 就不必记住是否要用 圆括号了, 都用就是了

        (2) 封装

            mem data 放 public -> 谁都可以 直接访问之

            `将 mem data 隐藏 在 函数 interface 背后`

                可为 `所有可能的实现` 提供弹性 
            
                1) 实现出
                    
                    `不准访问 / 只读 / 只写 / 读写`

                2) `mem data 被 读/写 时, 可 notify 其他对象`

                3) 验证 class 约束条件 

                4) `多线程 中 同步`

                `5) 日后可以 改为 某个计算 替换( 省掉 mem data 所占内存 / 速度慢 )` 该 mem data, 
                        而 client 不会知道内部已变化

                    这般能力 等价于 其他语言中的 `properties`

                        // 不准访问/只读/只写/读写
                        class AccessLevel
                        {
                        public:
                            int getReadOnly() const { return readOnly; }
                            void setWriteOnly(int value) { writeOnly = value; }
                            
                            int getReadWrite() const { return readWrite; } 
                            void setReadWrite(int value) { readWrite = value; } 
                        private:
                            int noAccess;
                            int readOnly;
                            int writeOnly;
                            int readWrite;
                        };

                            一直计算并更新 平均速度 放 mem data, 调用时直接读 mem data 值 
                                vs. 每次调用时, 才计算平均速度 并 返回

                            mem data 占内存, 但 调用时 速度快 
                                vs. 不多占内存, 但 调用时 速度慢

    2    mem data 为什么 不采用 protected

            理由 与 public 相近

                从 `封装` 角度看, `只有2种 访问权限: private ( 提供 封装 ) 和 其他 ( 不提供 封装 )`

6 选 non-mem / non-friend 还是 mem func

    1    non-mem func 优于 mem func` 的 2个地方`: 封装性 更高 & 包裹弹性 更大`
                                                    |               |
                                                    |               |   =>
                                                    |/              |/
                                                与 直观相反      编译依赖 更低
                                                                                                
        (1) `与 直观相反, non-mem func 封装性 高于` mem func

            OO 要求 data 尽可能 被 封装

            OO 建议 `data 和 操作 data 的 函数 应 捆绑在一起`
                => 貌似 mem func 比 non-mem func 好, 但 该建议 `并不正确`
    
        (2) `包裹弹性 更大 => 编译依赖 更低`
    
            class WebBrowser
            {
            public:
                void clearCache();   // 下载高速缓存
                void clearHistory(); // 访问过的 URLs 历史记录
                void clearCookies(); // cookies
            };

            // 1 整个操作, 执行 3 个 动作
            // (1) mem func
            class WebBrowser
            {
            public:
                void clearEverything(); // 调 前3者
            };

            // (2) non-mem non-friend func
            void clearBrowser(WebBrowser& wb)
            {
                wb.clearCache();
                wb.clearHistory();
                wb.clearCookies();
            }

    2    数据 `封装性`

            `越少 code 能 访问 数据, 数据 封装性越高
                
                改变数据 对 clients 影响越少`

            以 `能访问数据的 函数个数` 衡量 `数据封装性:`
                越少 函数 能访问 -> 封装性 越高

    3   `utility func` 提高封装性 + `分离编译依赖`、

7 若 所有 参数 ( 包括 this 所指 *this: 如 运算符重载函数 的 lhs 相应实参 ) 均 需 type conversion, 则 用 non-mem func

8 不抛出异常swap

    0   swap 应用
        
        ——————————————————————————————————
        [1] `exception-safe 编程 的脊柱`
        ——————————————————————————————————
        [2] 处理 `自我赋值`
        ——————————————————————————————————
            swap: 置换两对象的值

    1   缺省 swap

        std::swap 典型实现: 引用传递 + 3 次 copy 
                                              |
                                              |  要求 
                                              |/
                                        T 支持 copying
                                        
            namespace std
            {
                template<typename T>
                void swap(T& a, T& b)
                {
                    T temp(a);
                    a = b;
                    b = temp;
                }
            }
    
            问题: 对 某些 类型, 3 次复制 无一必要
                        |
                        |   如
                        |/
                用 pimp 手法 的 类型

    2   `用 pimp 手法 的 类型 A` 下 针对 `T 为 A` 设计 `std::swap 全特化` 版本

            (1) 缺省 swap 问题 
                
                效率低
                    
                    `不止 copy 3 个 对象, 还 copy 3 个 Impl 对象`
                            class AImpl  // 针对 A 设计的 class
                            {
                            private:
                                int a, b, c;
                                std::vector<double> vec; // 意味着 copy 时间很长
                            };

                            class A
                            {
                            public:
                                A(const A& rhs);

                                // 复制 A 时, 还复制 其 AImpl 对象
                                A& operator=(const A& rhs)
                                {
                                    // ...
                                    *pImpl = *(rhs.pImpl);
                                    // ...
                                }
                            private:
                                AImpl* pImpl; // pImpl 指针, 所指对象 内含 A 数据
                            };                      
                    |
                    |   解决 
                    |/
                  思路
                        `swap 2 个 对象, 只需 swap 其 内部 pImpl 指针`

                            `std::swap 全特化: 目标 类型 T == A`

                                通常, `不允许 改变` std namespace 内 任何东西,
                                    但 可以 为 标准 template ( 如 swap ) 建 特化版本,
                                        使之专属于 自建 class

                                    namespace std
                                    {
                                        template<>
                                        void swap<A> (A& a, A& b)
                                        {
                                            swap(a.pImpl, b.pImpl);
                                        }
                                    }
                    |
                    |   问题 
                    |/
                A 对象 直接访问 private 成员 => 编译不过
                    |
                    |   解决
                    |/
            std::swap `全特化 版` -> 调 `A 成员函数 swap` -> 调 标准 swap: internal ptr 作 实参    

                与 STL 容器 做法相同

                    class A
                    {
                    public:
                        void swap(A& rhs)
                        {
                            using std::swap;
                                   /|
                                  /     对 internal ptr 调 标准 swap 
                                 /
                            swap(pImpl, rhs.pImpl); // class 内 本 class 任何对象 可直接访问其 private mem
                        }       |\
                                |
                    };           - - - - - - - -                
                                                |
                    namespace std               |
                    {                           |
                        // 全特化: 对 A         | 
                        template<>              |
                        void swap<A>(A& a, A& b)|
                        {                       |
                            a.swap(b);  - - - - - 
                        }
                    };

    3   泛化: A / AImpl 均为 class template

        template<typename T>
        class AImpl{ ... };

        template<typename T>
        class A{ ... };

        (1) std 内 偏特化 template func -> 编译报错 
                    
                C++ `只 允许 偏特化 template class`
                C++ `不 允许 偏特化 template func`

                    namespace std
                    {
                        template<typename T>
                        void swap< A<T> >(A<T>& a, A<T>& b)
                        {
                            a.swap(b);
                        }
                    };
                |
                |   解决 
                |/
        (2) `添加 templete func 重载`

                std 内 添加 template func 重载 -> 编译报错 
                        
                    std 内   `不可 添加 新的 template, 可 全特化 template`
                    
                        namespace std
                        {
                            template<typename T>
                            void swap(A<T>& a, 
                                      A<T>& b)
                            {
                                a.swap(b);
                            }
                        };
                |
                |   解决 
                |/
        (3) `自定义 namespace` 内 添加 template func 重载 ( swap ) -> 调 `A::swap`

                这种做法 对 class / class template 都 ok

            ———————————————————————————————————————————————————————————————
        (4) `client 调 swap 匹配顺序` 
                ———————————————————————————————————————————————————————————
                [1] ADL 
                    `T 所在 namespace 之 T 专属 swap 
                                                |
                                                |   如: 调 
                                                |/
                                            T 内 成员函数 swap 
                                                |
                                                |   调 
                                                |/
                                            internal ptr 作 实参 调 
                                                经 using 声明 引入的 
                                                    标准 swap // std::swap
                                                
                ———————————————————————————————————————————————————————————
                [2] std::swap 之 全特化版
                ———————————————————————————————————————————————————————————
                [3] std::swap 之 经典版
            ———————————————————————————————————————————————————————————————

            namespace N
            {
                template<typename T>
                class AImpl 
                {
                private:
                    int a, b, c;
                    std::vector<T> vec; 
                };

                template<typename T>
                class A
                {
                public:
                    template <typename T>
                    void swap(A<T>& rhs)
                    {
                        using std::swap;
                             /|
                            /
                        swap(pImpl, rhs.pImpl); 
                    }     |\
                          |
                           - - - - - - - 
                private:                |
                    AImpl<T>* pImpl;    |
                };                      |
                                        |
                template<typename T>    |
                void swap(A<T>& a,      |
                          A<T>& b)      |
                {                       | 
                    a.swap(b); - - - - -
                }       |\
            }           |
         - - - - - - - -    
        |
        |   template<typename T>
        |   void f(T& obj1, T& obj2)
        |   {
        |       // [1] 令 std::swap 在 该 scope 内 可用
        |       using std::swap; 
        |
        |       // [2] ADL: 不必 using T_namespace::swap
        |_ _ _ _swap(obj1, obj2); // 如 T == A 或 N::A<double> 
            }
            
            ————————————————————
            test
            ————————————————————    
                T 为 3 种 类型 
                ————————————————
                    [1] 内置类型 
                    [2] 类 
                    [3] 类模板 
            ————————————————————
            
            #include <vector>

            //--- 1     class
            class AImpl_2
            {
            private:
                int a, b, c;
                std::vector<double> vec;
            };

            class A_2
            {
            public:
                void swap(A_2& rhs)
                {
                    using std::swap;
                    swap(pImpl, rhs.pImpl);
                }
            private:
                AImpl_2* pImpl;
            };

            //(3) std::swap 全特化版 
            namespace std
            {
                template<>
                void swap(A_2& a, A_2& b) { a.swap(b); }
            };

            //--- 2 class template
            namespace N
            {
                template<typename T>
                class AImpl
                {
                private:
                    int a, b, c;
                    std::vector<T> vec;
                };

                template<typename T>
                class A
                {
                public:
                    template <typename T>
                    void swap(A<T>& rhs)
                    {
                        using std::swap;
                        swap(pImpl, rhs.pImpl);
                    }
                private:
                    AImpl<T>* pImpl;
                };

                //(2) T 所在 namespace 之 T 专属 swap
                template<typename T>
                void swap(A<T>& a,
                          A<T>& b)
                {
                    a.swap(b);
                }
            }

            template<typename T>
            void f(T& obj1, T& obj2)
            {
                using std::swap; // 令 std::swap 在 此函数内 可用

                // ADL
                swap(obj1, obj2);
            }

            int main()
            {
                int a = 0;
                int b = 1;

                N::A<double> w1;
                N::A<double> w2;

                A_2 w3;
                A_2 w4;

                // (1)
                f(a, b);

                // (2)
                f(w1, w2);

                // (3)
                f(w3, w4);
            }

    4   mem swap 绝不可抛出异常

        `swap 的 最好应用: 提供 `exception-safety`

        `自定义 swap` 版本 往往同时提供
            
            `高效 swap` 和 exception-safety`
                    |
                    |                       其上 操作 绝不会 抛出异常
                    |                           |\
                    |                           |
                    |/                          |
                `高效 swap` 几乎总是 基于 `内置操作` 
                                                |
                                                |   如 
                                                |/
                                            pImpl 手法 的 internal ptr

相关文章

网友评论

      本文标题:Effective C++ 4: Design & Declar

      本文链接:https://www.haomeiwen.com/subject/uuwrmltx.html