C++的代理類

mug發表於2021-09-09

怎樣在一個容器中包含型別不同,但是彼此有關係的物件?眾所周知,C++的容器只能存放型別相同的元素,所以直接在一個容器中儲存不同型別的物件本身是不可能的,只能透過以下兩種方案實現:

1. 提供一個間接層,在容器中存放物件的指標而不是物件本身。

2. 透過代理類實現。

圖片描述

class Animal
{public:    virtual void Eat() = 0;
};class Cat : public Animal{};class Dog : public Animal{};class Bird : public Animal{};

圖片描述

在上面我們看到有一個虛基類和三個繼承類,下面分別用兩種方案來實現一個容器存放不同型別但又互相關聯的類。

1.透過指標實現

圖片描述

Animal* animals_array[3];
Cat cat;
Dog dog;
Bird bird;
animals_array[0] = &cat;
animals_array[1] = &dog;
animals_array[2] = &bird;

圖片描述

這樣會帶來一個問題,就是容器中的指標指向的物件如果被銷燬,這個指標就會變成野指標,就像下面這樣:

圖片描述

Animal* animals_array[3];do{
    Cat cat;
    Dog dog;    
    Bird bird;
    animals_array[0] = &cat;
    animals_array[1] = &dog;
    animals_array[2] = &bird;
}while(0);//此時物件已經被析構,容器中的指標指向未知內容

圖片描述

也可以換一種方式,構造一個新的動態物件,將其地址放在容器中,這樣就可以避免物件析構導致指標失效的問題:

Cat cat;
animals_array[0] = new Cat(cat);

這樣會曾加額外的記憶體開銷,並且可能出現容器中兩個指標同時指向一個物件的情況。
所以,在容器中存放不同物件的指標並不是一個很好的解決方案。

2.透過代理類實現

實現程式碼如下:

圖片描述

class Animal
{public:    virtual void Eat() = 0;    //copy函式,構造一個基於自身物件型別的物件
    virtual Animal* copy() const = 0;    virtual ~Animal() {}
};class Cat : public Animal
{public:    virtual void Eat()
    {
        std::cout copy() : nullptr;
    }    ~AnimalSurrogate()
    {        if (pa != nullptr)
        {            delete pa;
            pa = nullptr;
        }
    }    //過載 = 運算子
    AnimalSurrogate& operator=(const AnimalSurrogate& ani_srg)
    {        if (this != &ani_srg)
        {            delete pa;
            pa = ani_srg.pa != nullptr ? ani_srg.pa->copy() : nullptr;
        }        return *this;
    }    //將基類中的公共函式搬過來,這樣就可以透過代理類直接訪問這些方法
    void Eat()
    {        if (pa == nullptr)
        {            throw "empty AnimalSurrogate.Eat()";
        }        return pa->Eat();
    }private:
    Animal* pa;//儲存基類的指標};

圖片描述

透過程式碼可以看出來,所謂的代理類,就是構造一個新的類,這個類中包含關聯的基類型別的指標,該指標可以指向不同型別但又相互關聯的子類物件,透過指標可以轉調物件的方法,同時實現記憶體的管理。代理類的實用方法如下:

圖片描述

Cat cat;
Dog dog;
Bird bird;

arr[0] = AnimalSurrogate(cat);
arr[1] = AnimalSurrogate(dog);
arr[2] = AnimalSurrogate(bird);

arr[0].Eat();//輸出 cat eat.arr[1].Eat();//輸出 dog eat.arr[2].Eat();//輸出 bird eat.

圖片描述

總結:代理類的的每個物件都代表另一個物件,該物件可以使位於一個完成繼承層次中的任何類的物件。透過在容器中用代理物件而不是物件本身的方式,實現容器中存放不同型別的物件

使用代理類的優缺點如下:

  • 優點:使用代理類比直接在容器中存放不同物件的指標更安全,便於實現記憶體管理。

  • 缺點:記憶體開銷太大,放入容器的每個物件都需要複製一次,不夠靈活。

為了避免物件的複製,可以透過控制程式碼類來實現,關於控制程式碼類的原理和使用在下一篇問文章中作介紹。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/506/viewspace-2801659/,如需轉載,請註明出處,否則將追究法律責任。

相關文章