怎樣在一個容器中包含類型不同,但是彼此有關系的對象?眾所周知,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 << "cat eat." << std::endl; } virtual Animal* copy() const { // 返回一個以自身作為參數構造的Cat類型的對象 return new Cat(*this); } }; class Dog : public Animal { public: virtual void Eat() { std::cout << "dog eat." << std::endl; } virtual Animal* copy() const { // 返回一個以自身作為參數構造的Dog類型的對象 return new Dog(*this); } }; class Bird : public Animal { public: virtual void Eat() { std::cout << "bird eat." << std::endl; } virtual Animal* copy() const { // 返回一個以自身作為參數構造的Bird類型的對象 return new Bird(*this); } }; //代理類 class AnimalSurrogate { public: AnimalSurrogate() :pa(NULL) {} AnimalSurrogate(const Animal& ani) { pa = ani.copy(); } //拷貝構造 AnimalSurrogate(const AnimalSurrogate& ani_srg) { pa = ani_srg.pa != nullptr ? ani_srg.pa->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.
總結:代理類的的每個對象都代表另一個對象,該對象可以使位於一個完成繼承層次中的任何類的對象。通過在容器中用代理對象而不是對象本身的方式,實現容器中存放不同類型的對象。
使用代理類的優缺點如下:
- 優點:使用代理類比直接在容器中存放不同對象的指針更安全,便於實現內存管理。
- 缺點:內存開銷太大,放入容器的每個對象都需要拷貝一次,不夠靈活。
為了避免對象的拷貝,可以通過句柄類來實現,關於句柄類的原理和使用在下一篇問文章中作介紹。