美文网首页
第3章:抽象机制-C++程序设计语言

第3章:抽象机制-C++程序设计语言

作者: my_passion | 来源:发表于2022-06-20 23:05 被阅读0次

3.0 建议

1 类

    (1) 类: 表示 应用中的 概念

    (2) 具体类: 表示 `简单概念 或 性能关键的组件`
    (3) 抽象类: 接口 和 实现 需 `完全分离` 时, 用作 接口
    (4) 区分 接口继承 和 实现继承
    (5) 成员 v 的 dtor 被 所属类 的 dtor 隐式调用
    
2 资源 & RAII & 容器
     (1) 管理资源: 资源句柄 & RAII 
     (2) 不要泄露 任何你认为是 资源的东西
            
3 容器
    (1) 同类型值 集合 -> 存到 容器: 作 资源管理 (类)模板

    (2) 值方式 return 容器: 移动 -> 高效

4 算法                     
    (1) 通用算法: 函数模板

    (2) 策略和操作: 函数对象 ( lambda 表达式)  

5 统一的 符号表示法: 类型别名/模板别名

3.1 类: 只考虑 3种

1 具体类 (型)

(1) 思想
    其 行为 "就像 内置类型 一样"
            
    complex 像 int 
    vector/string 像 内置数组
    
(2) 特征
    其 `表现形式 是 定义的一部分`
            
    1) 表现形式
        非指针(complex)
                
        指针(vector) -> 指向 自由存储 
                    -> 时空最优
                    
        不需要直接访问 类 的 表现形式 的函数, 作 非成员函数
        
    2) => 允许
        将 对象 放 栈/静态内存/其他对象中
        直接引用对象
        创建对象后立即初始化
        copy 对象
            
(3) 代价
    表现形式 任何明显变动 -> user 必须重新编译
            
(4) 容器
        RAII
    初始化列表 ctor
    编译器会自动为列表{...}会创建1个initializer_list对象

    class Vector
    {
    public:
        Vector(int s) : elem{ new double[s] }, sz(s)
        { 
            for (int i = 0; i != s; ++i) 
                elem[i] = 0; 
        }

        ~Vector() { delete[] elem; }

        double& operator[](int);
        int size() const;
    private:
        double* elem;
        int sz;
    };

    // 
    class Vector
    {
    public:             
        Vector(std::initializer_list<double>);
    }

    Vector(std::initializer_list<double> lst)
        : elem{ new double[lst.size()] }, sz{ lst.size() }
    {
        std::copy(lst.begin(), lst.end(), elem);
    }   

2 抽象类 (型)

(1) 思想

    `user` 与 类的 实现细节 `完全分离`
    
    优势
         只要接口不变, 即使 实现变 => user 也 不需要重新编译
        
            user(Container&) 可以在 完全不了解 抽象类 Container 实现细节(哪个派生类实现 & 如何实现) 的 情况下 
            使用 抽象类 的 接口函数
        
    分离 接口 与 表现形式, 且 放弃 纯局部变量
                |
                | 
                |/
    user 对 其 表现形式 一无所知(甚至不知其 大小)
                |
                |
                |/
(2) 特征
    必须从 自由存储 为 对象分配空间, 
    并通过 指针或引用 访问对象
    
(3) virtual 含义: "可能在 派生类中 重新定义"
                |   
                | = 0
                |/
    纯虚函数: 派生类 必须定义该 纯虚函数
                |   
                | 含 纯虚函数 的 类
                |/
            抽象类
                负责为派生类提供 接口 => 抽象类 称为 多态类型
                    不能单纯定义 其 对象

    虚函数
        
        用 引用/指针 访问 虚成员函数时, 如何解析到 正确的版本?
        
        答: 虚调用 机制
        
            vptr + vtbl
            
            比 普通函数调用 效率差 <= 25% 

    // 纯粹的 接口类
    class Container
    {
    public:
        virtual double& operator[](int) = 0;
        virtual int size() const = 0;
        virtual ~Container() {}
    };
        |  
        |/
    void user(Container& c)
    {
        const int sz = c.size();
        for(int i=0; i!= sz; ++i)
            std::cout << c[i] << "\n";
    }
    
    class Vector_container: public Container // 继承自 抽象类
    {
    private:
        Vector v; // 具体类 作成员
    public:
        Vector_container(int s): v(s) {}
        ~Vector_container(){} 
        
        virtual double& operator[](int) { return v[i]; } // 转发: 让 成员 去做
        virtual int size() const { return v.size(); }
    };

3 类层次

(1)
    1) 接口继承
    
    基类 像 派生类 的 `接口` 一样, 通常是 抽象类
        
        抽象基类必须用 `虚dtor`
            
        保证用 抽象基类指针/引用释放派生类对象时, 
        能正确调用 派生类dtor:
        隐式调基类dtor + 成员的dtor
            
        delete pBase 会调用相应派生类的 dtor
            派生类可能有需要释放的资源

    2) 实现继承
        基类 用于 `简化 派生类 实现`, 通常含 成员数据 和 ctor

(2) 合并 容器的遍历 + 对各元素的具体操作 
        |
        | 繁琐 + 依赖
        |
        | 解决
        |/          
    分离 ... + ... 
            
        容器 elem: rawPtr/SP
            *elem: 所指 resource 
                
        for_all(容器引用, 策略对象) 
             |                  | 
             |                  | 
             |                  |
            函数模板     函数对象 / lambda
        
        好处 
            [1] 函数模板 不依赖于 具体(派生)容器 
                
            [2] 策略对象 不 care resource 的 存储方式了 (指针/SP 都行)
                处理 resource 的引用
                <=> operator() 的 参数为 resource 的引用 

    // draw_all 是 user 用的 ... 非成员函数     
    void draw_all(vector<Shape*>& v) 
    {
        for(auto p: v)
            p->draw(); // draw 是 resource 的 成员函数
    }
    void print_all(vector<Shape*>& v) 
    {
        for(auto p: v)
            p->print(); 
    }
            |
            |  分离 容器的遍历 + 对各元素的具体操作
            |/
    template <typename C, Oper op>
    for_all(C& c, Oper op)  // for_all 不依赖于 具体(派生类) 容器
    {
        for(auto& elem: c)
            op(*elem);      // op 处理 容器中 resource 的引用
    }           |
                |_ _ _ _ _ _ _ _ _ _ _ _ 
                                        |  
    void user()                         |
    {                                   |
        vector< unique_ptr<Shape> > v;  |
        // fill v           _ _ _ _ _ _ |       
                           |  lambda 很便捷
                           |/ 
        for_all(v, [](Shape& s) { s.draw(); } );
        for_all(v, [](Shape& s) { s.print(); } );
    }                      |\
                           |_ _ _ _
                                   |
    template <typename Resource>  | 
    class Oper                     |
    {                              |
        // 状态变量: ctor 初始化   |
        // T& val;                 |
    public:                        |
        Oper(): {}                 |
                                  /
        void operator()(Resource& r) const 
        { 
            cr.draw();
        } 
    };  

3.2 copy 和 move

1 copy 容器

copy 的默认含义: 逐成员 copy
    
(1) 资源句柄类 (string / vector / SmartPointer / thread / fstream ): 字符串句柄 / 动态内存句柄 / 线程句柄 / 文件句柄
    负责 通过指针 访问对象
        |
        |
        |/
    逐成员 copy: 违反 资源句柄的不变式(resource 只能被释放1次)
        副本 析构 -> 原 handle 所管理的 resource 被释放
        |
        |
        |/ 
     copy 的正确含义: 分配空间 + 资源 copy
         
(2) 同一个类 的 2个对象 互为友员, 对象1的 memFunc 中可直接 用 对象2访问对象2的 private data
    A(const A& rhs)
    {
        x = rhs.x;
    }
    
    // copy ctor        
    Vector::Vector(const Vector& rhs)
        : sz(rhs.sz), elem{new double[sz]}
    {
        for(int i=0; i!=sz; ++i)
            elem[i] = rhs.elem[i];
    }
        
    // copy assignment
    Vector& Vector::operator=(const Vector& rhs)
    {
        // 1) 分配空间: note 先用 暂存指针 接管
        double* p = new double[rhs.sz];
        
        // 2) copy 资源
        for(int i=0; i!=sz; ++i)
            p[i] = rhs.elem[i];
        
        // 3) 删 旧资源
        delete[] elem; 
        
        // 4) 接管 新资源
        elem = p;
        sz = rhs.sz;
        
        return *this;
    }

2 move 容器 & 资源管理

    (1) 不希望 或 不能 copy, 只希望 移动 资源所有权
        不希望 copy Vector<double> 
        不能   copy Vector<thread>
                
    (2) 移动 后, 源对象 进入的状态 应该允许 运行 dtor, 通常, 也应该允许 被 赋值 
    
    移动操作: 允许 资源/对象 从 一个 scope 移动到 另一 scope
    
    // 移动 ctor
    Vector::Vector(Vector&& rhs)
        : elem(rhs.elem), sz(rhs.sz)
    {
        rhs.elem = nullptr; 
        rhs.sz = 0;
    }
    
    std::vector<thread> threads;
    
    Vector init(int n)
    {
        thread t {f};
        threads.push_back( move(t) );
        //...
        Vector vec(n);
        for(int i=0; i<vec.size(); ++i) 
            vec[i] = 10;
        return vec; 
    }

3 抑制操作:=delete

(1) delete 默认 copy 操作, 但 确实希望 copy 类层次 中 某对象
    clone() 函数: 返回类型协变 (22.2 节)
    通过抽象类(Io_obj) 将 1个类 (Io_circle) 纳入 已有 类层次(Circle)
                
(2) =delete 机制是通用 的, 可用于 抑制任何操作

3.3 模板

1 参数化 类型

    (1) 用 元素类型 参数化 容器类型     
        
        模板参数 T
            含义: 对 所有类型 T
            
    (2) 为支持 范围 for 循环, 定义 begin()/end()
            
    (3) 模板是 编译时机制, 不会产生 额外 运行时开销
            
    template <typename T>
    class Vector
    {
    private:
        T* elem; 
        int sz;
    public:
        Vector(int s);  // ctor: 建立不变式, 获取资源
        ~Vector() {delete[] elem; } 
        
        // ...
        
        T& operator[](int);
        const T& operator[](int) const;
        
        int size() const;       
    };

    // 重载版本
    template <typename T>
    T* begin(Vector<T>& x)
    {
        return &x[0];
    }
    template <typename T>
    T* end(Vector<T>& x)
    {
        return x.begin() + x.size();
    }

2 函数模板

3 函数对象

    (1) 谓词
            
        返回 bool值 的 函数对象
                        
    (2) 策略对象
            
        通用算法 `操作` 含义 的 函数对象
          |         |
        count()     Less_than
        
    (3) lambda vs. 函数对象
            
        —————————————————————————————————————————————————————————————————————————————————————————————————————
        算法          |   算法 
        —————————————————————————————————————————————————————————————————————————————————————————————————————
        lambda 表达式 |  捕获列表: 捕获方式                                      | paraList
        —————————————————————————————————————————————————————————————————————————————————————————————————————
        函数对象      |  按捕获方式 存(内部状态)+传(Ctor 参数) capturelist 成员   | operator()(...)的 paraList
        —————————————————————————————————————————————————————————————————————————————————————————————————————
    
    #include <vector>
    #include <iostream>
    using namespace std;

    template<typename C, typename P>
    int count(const C& c, P pred)
    {
        int cnt = 0;
        for (const auto& x : c)
            if (pred(x))
                ++cnt;
        return cnt;
    }

    template <typename T>
    class Less_than
    {
        const T val; // 状态变量: ctor 初始化
    public:
        Less_than(const T& v) : val(v) {}
        bool operator()(const T& x) const // 函数调用运算符
        { return x < val; } 
    };

    void f1(vector<int>& vec, int x)
    {
        cout << count(vec, Less_than<int>(x) ) << endl;
    }

    void f2(vector<int>& vec, int v)
    {
        cout << count(vec, [&](const int& x) { return x < v; } ) << endl;
    }

    int main()
    {
        vector<int> vec{ 1, 2, 3 };

        f1(vec, 3);
        f2(vec, 3);
    }

4 可变参数模板 ( 详见 28.6 节 )

    优点
        参数个数+类型 可任意组合
                
    缺点
        接口 的 类型检查 复杂
        
    #include <iostream>     
    template <typename T>
    void g(T t)
    {
        std::cout << t << " ";
    }

    void f() {}     // 3) 递归终止

    template <typename T, typename... Tail>
    void f(T head, Tail... tail)
    {
        g(head);    // 1) 先处理 第1参数
        f(tail...); // 2) 用 剩余参数(tail) 递归调 f()
    }

    int main()
    {
        f(1, 2.1, "hello");
    }   

5 别名

    标准库容器 都提供了 value_type 作其 值类型 name
            
    template <typename T>
    class Vector
    {
    public:
        using value_type = T;
    };

相关文章

网友评论

      本文标题:第3章:抽象机制-C++程序设计语言

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