• c++ 沉思录——代理类


    代理类

    引言

    怎么设计一个c++容器,使它有能力包含类型不同而彼此相关的对象呢?
    容器通常只能包含一种类型的对象,所以很难在容器中存储对象本身。
    存储指向对象的指针,虽然允许通过继承来处理类型不同的问题,但是也增加了内存分配的额外负担。
    这里讨论一种方法,通过定义名为 代理(surrogate) 的对象来解决该问题。代理运行起来和它所代表的对象基本相同,但是允许将整个派生层次压缩在一个对象类型中。

    5.1 问题

    假设有不同种类的交通工具的类派生层次,不同的派生类都有一些基类的共有属性,但也有一些自己独有的属性(飞翔、盘旋等)。下面假设我们要处理一系列不同种类的Vehicle。
    基类:Vehicle
    派生类:RoadVehicle AutoVehicle Aircraft …

    5.2 经典解决方案

    常见做法:提供一个间接层(indirection)。
    最早的合适的间接层形式就是存储指针,而不是对象本身:Vehicle* parking_lot[1000]; //指针数组
    然后输入类似:

    Automobile x = /*...*/;    
    Parking_lot[num_vehicles++] = &x;  
    
    • 1
    • 2

    的语句。
    带来了两个新问题:
    问题1: 首先x是局部变量,一旦x没有了,parking_lot就不知道指向哪里了。
    变通:放入parking_lot中的值,不是指向原对象的指针,而是指向原对象的副本的指针。然后,采用一个约定,就是当我们释放parking_lot时,也释放其中所指向的全部对象。因此,把前面例子改为:

    Automobile x = /*...*/;    
    Parking_lot[num_vehicles++] = new Automobile(x); 
    
    • 1
    • 2

    尽管这样修改可以不用存储指向本地对象的指针,它也带来了动态内存管理的负担。
    另外,只有当我们知道要放到parking_lot中的对象是静态类型后,这种方法才能起作用。(这是为什么?)
    如果不知道会怎么样?例如,假设我们想让parking_lot[p]指向一个新建的vehicle,这个vehicle的类型和值与parking_lot[q]指向的对象相同,情况会怎么样?那么,我们将不可以使用以下的语句:

    if(p != q) {
        delete parking_lot[p];
        parking_lot[p] = parking_lot[q];
    }
    
    • 1
    • 2
    • 3
    • 4

    因为接下来parking_lot[p] 和 parking_lot[q]将指向相同的对象;我们也不可以使用以下的语句:

    if(p != q) {
        delete parking_lot[p];
        parking_lot[p] = new Vehicle(parking_lot[q]); // 没有Vehicle类型
    }  
    
    • 1
    • 2
    • 3
    • 4

    5.3 虚复制函数

    想一个办法:来复制编译时类型未知的对象。我们知道,c++中处理未知类型的对象的方法就是使用虚函数。这种函数的一个显而易见的名字就是copy。
    由于我们是想能够复制任何类型的Vehicle,所以应该在Vehicle种增加一个合适的纯虚函数:

    class Vehicle {
    public:
        virtual double weight() const = 0; // 函数后加const,这些成员函数不改变类的数据成员,也就是说,这些函数是"只读"函数
        virtual void start() = 0;
        virtual Vehicle* copy() const  = 0;
        // ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接下来,在每个派生自Vehicle的类中增加一个新的成员函数copy。
    指导思想:如果vp指向某个继承自Vehicle的不确定类的对象,则vp->copy()会获得一个指针,该指针指向该对象的一个副本。
    例如,如果Truck继承自(直接或间接)Vehicle,则它的copy函数类似于:

    Vehicle* Truck::copy() const {
        return new Truck(*this);
    }  
    
    • 1
    • 2
    • 3

    当然,处理完一个对象后,需要清除该对象。要做到这一点,Vehicle就必须有一个虚析构函数。

    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    class Vehicle {
    public:
        Vehicle() {
            cout << "Vehicle 构造函数" << endl;
        }
        virtual ~Vehicle() {
            cout << "Vehicle 析构函数" << endl;
        }
    public:
        virtual Vehicle* copy() const = 0;
        virtual void say() const = 0;
    };
    
    class Truck : public Vehicle {
    public:
        Truck() {
            cout << "Truck 构造函数" << endl;
        }
        ~Truck() override {
            cout << "Truck 析构函数" << endl;
        }
    public:
        Vehicle* copy() const override {
            cout << "Truck copy()" << endl;
            return new Truck(*this);
        }
        void say() const override {
            cout << "Truck say" << endl;
        }
    
    };
    
    int main() {
        auto vp = make_shared<Truck>();
        auto vehicle = vp->copy();
        vehicle->say();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    5.4 定义代理类

    有没有一种方法:既能使我们避免显示地处理内存分配,又能保持Vehicle类在运行时绑定的属性呢?

    答案:定义一个行为和Vehicle对象相似、而又潜在地表示了所有继承自Vehicle类的对象的东西。我们把这种类的对象叫做 代理(surrogate)

    每个Vehicle代理都代表某个继承自Vehicle类的对象。只要该代理关联着这个对象,该对象肯定存在。因此,复制代理就会复制相对应的对象,而给代理赋新值也会先删除旧对象,在复制新对象。

    class VehicleSurrogate {
    public:
        VehicleSurrogate() = default;
        VehicleSurrogate(const Vehicle&);
        ~VehicleSurrogate() = default;
        VehicleSurrogate(const VehicleSurrogate&);
        VehicleSurrogate& operator=(const VehicleSurrogate&);
        
    private:
        Vehicle *vp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上述代理类有一个以 const Vehicle& 为参数的构造函数,这样就可以为任何继承自 Vehicle类的对象创建代理了。
    同时,代理类还有一个默认的构造函数,所以可以创建 VehicleSurrogate 对象的数组了。

    然后,默认的构造函数带来了新问题:如果 Vehicle 是抽象基类,我们应该如何规定 VehicleSurrogate 的默认操作呢?
    它所指向的对象的类型是什么呢?不可能是 Vehicle,因为根本没有 Vehicle对象。

    为了有更好的办法,我们要引入行为类似零指针的**空代理(empty surrogate)**的概念。能够创建、销毁和复制这样的代理,
    但是进行其他操作就视为出错。ok,下面是成员函数的定义:

    class VehicleSurrogate {
    public:
        VehicleSurrogate() : vp(nullptr) {
            cout << "VehicleSurrogate 默认构造函数" << endl;
        }
        explicit VehicleSurrogate(const Vehicle& v) : vp(v.copy()) {
            cout << "VehicleSurrogate(const Vehicle& v) " << endl;
        }
        ~VehicleSurrogate() {
            cout << "VehicleSurrogate 析构函数" << endl;
            delete vp;
        }
        VehicleSurrogate(const VehicleSurrogate& v) : vp(v.vp ? v.vp->copy() : nullptr) {
            cout << "VehicleSurrogate 复制构造函数" << endl;
    
        }
        VehicleSurrogate& operator=(const VehicleSurrogate& v){
            if (this != &v) {
                delete vp;
                vp = (v.vp ? v.vp->copy() : nullptr);
            }
            return *this;
        }
    
    private:
        Vehicle *vp;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    3个技巧要注意:
    1)每次对copy的调用都是一次虚拟调用。这些调用只能是虚拟的,别无选择,因为类Vehicle的对象不存在。
    即使是在那个只接收一个 const Vehicle& 参数的复制构造函数中,它所进行的 v.copy调用也是一次虚拟调用,
    因为v是一个引用,而不是一个普通对象。

    2)注意 复制构造函数和赋值操作符中的 v.vp 非零的检测。这个检测是必须的,否则调用v.vp->copy() 时就会出错。

    3)注意 对赋值操作符进行检测,确保没有把代理赋值给它的自身。

    下面令该代理类支持 Vehicle类所能支持的其他操作。我们把start() 和 weight() 加入类VehicleSurrogate 中。

    注意,VehicleSurrogate 类中的start 和weight 不是虚函数,我们使用的对象都是 VehicleSurrogate 类的对象;
    没有继承自Vehicle对象。当然函数本身可以调用相应的 Vehicle对象的虚函数。
    它们也应该检查确保 vp 不为零:

    class VehicleSurrogate {
    public:
        VehicleSurrogate() = default;
        VehicleSurrogate(const Vehicle&);
        ~VehicleSurrogate() = default;
        VehicleSurrogate(const VehicleSurrogate&);
        VehicleSurrogate& operator=(const VehicleSurrogate&);
        
    public: // 类Vehicle支持的操作
        double weight() const {
            if (!vp)
                throw "empty VehicleSurrogate.weight()";
            return vp->weight();
        }
        void start() const {
            if (!vp)
                throw "empty VehicleSurrogate.start()";
            return vp->start();
        }
        // ...
    
    private:
        Vehicle *vp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    接下来,我们就很容易定义我们的停车场了(parking_lot)

    VehicleSurrogate parking_lot[5];
    Truck x;
    parking_lot[2] = VehicleSurrogate(x);
    
    
    • 1
    • 2
    • 3
    • 4

    最后这个语句创建了一个关于对象x的副本,并将 VehicleSurrogate 对象绑定到该副本。

    以下是完整代码

    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    class Vehicle {
    public:
        Vehicle() {
            cout << "Vehicle 构造函数" << endl;
        }
        virtual ~Vehicle() {
            cout << "Vehicle 析构函数" << endl;
        }
    
    public:
        virtual Vehicle* copy() const = 0;
        virtual void say() const = 0;
        virtual double weight() const = 0;
        virtual void start() const = 0;
    };
    
    class Truck : public Vehicle {
    public:
        Truck() {
            cout << "Truck 构造函数" << endl;
        }
        ~Truck() override {
            cout << "Truck 析构函数" << endl;
        }
    public:
        Vehicle* copy() const override {
            cout << "Truck copy()" << endl;
            return new Truck(*this);
        }
        void say() const override {
            cout << "Truck say" << endl;
        }
        double weight() const override {
            cout << "Truck weight" << endl;
        }
        void start() const override {
            cout << "Truck start" << endl;
        }
    
    };
    
    class VehicleSurrogate {
    public:
        VehicleSurrogate() : vp(nullptr) {
            cout << "VehicleSurrogate 默认构造函数" << endl;
        }
        explicit VehicleSurrogate(const Vehicle& v) : vp(v.copy()) {
            cout << "VehicleSurrogate(const Vehicle& v) " << endl;
        }
        ~VehicleSurrogate() {
            cout << "VehicleSurrogate 析构函数" << endl;
            delete vp;
        }
        VehicleSurrogate(const VehicleSurrogate& v) : vp(v.vp ? v.vp->copy() : nullptr) {
            cout << "VehicleSurrogate 复制构造函数" << endl;
    
        }
        VehicleSurrogate& operator=(const VehicleSurrogate& v){
            cout << "VehicleSurrogate 赋值构造函数" << endl;
            if (this != &v) {
                delete vp;
                vp = (v.vp ? v.vp->copy() : nullptr);
            }
            return *this;
        }
        double weight() const {
            if (!vp)
                throw "empty VehicleSurrogate.weight()";
            return vp->weight();
        }
        void start() const {
            if (!vp)
                throw "empty VehicleSurrogate.start()";
            return vp->start();
        }
        // ...
    private:
        Vehicle *vp;
    };
    
    int main() {
        VehicleSurrogate parking_lot[5];
        Truck x;
        parking_lot[2] = VehicleSurrogate(x);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    // 输出
    VehicleSurrogate 默认构造函数
    VehicleSurrogate 默认构造函数
    VehicleSurrogate 默认构造函数
    VehicleSurrogate 默认构造函数
    VehicleSurrogate 默认构造函数
    Vehicle 构造函数
    Truck 构造函数
    Truck copy()
    VehicleSurrogate(const Vehicle& v) 
    VehicleSurrogate 赋值构造函数
    Truck copy()
    VehicleSurrogate 析构函数
    Truck 析构函数
    Vehicle 析构函数
    Truck 析构函数
    Vehicle 析构函数
    VehicleSurrogate 析构函数
    VehicleSurrogate 析构函数
    VehicleSurrogate 析构函数
    Truck 析构函数
    Vehicle 析构函数
    VehicleSurrogate 析构函数
    VehicleSurrogate 析构函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    一文带你看透短信验证码
    Mongoose【node.js的优雅mongodb对象建模】
    用例图 UML从入门到放弃系列之三
    小白量化《穿云箭集群量化》(2)量化策略编写(1)
    DDD的哲学意味(上)
    微服务生态系统:使用Spring Cloud构建分布式系统
    Docker swarm 集群搭建
    [JavaScript]构造函数创造对象,基本包装类型
    PHP代码审计DVWA[XSS (DOM)]
    【Docker-k8s学习和实战】(三)Docker初试:启动第一个docker容器;docker容器的运行、停止、重启以及镜像的删除
  • 原文地址:https://blog.csdn.net/weixin_44557375/article/details/125454213