C++的代理类
怎样在一个容器中包含类型不同,但是彼此有关系的对象?众所周知,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.
总结:代理类的的每个对象都代表另一个对象,该对象可以使位于一个完成继承层次中的任何类的对象。通过在容器中用代理对象而不是对象本身的方式,实现容器中存放不同类型的对象。
使用代理类的优缺点如下:
- 优点:使用代理类比直接在容器中存放不同对象的指针更安全,便于实现内存管理。
- 缺点:内存开销太大,放入容器的每个对象都需要拷贝一次,不够灵活。
为了避免对象的拷贝,可以通过句柄类来实现,关于句柄类的原理和使用在下一篇问文章中作介绍。