一、类的设计的几个问题
1、数据成员变量私有化:
大多数public变量都是不安全的,我们无法保证使用者对它进行无意的篡改,对于一些成员变量,如果类外需要对其进行访问,可以通过两种方式对数据进行保护,一种是函数的调用,一种是引用的实现;如下代码所示:
函数的方式实现对数据的保护:(注:类成员函数后面加const修饰,表明成员函数隐式传入的指针是const指针,因此不能对类数据成员进行修改;)
class A { public : int Getlength() const ; private : int length; ...... } |
引用的实现:
2、对于数据成员要在构造函数里面进行初始化,在析构函数里面进行资源的释放
3、对于抽象类析构函数要声明为虚函数,保证对象的正确释放;而对于普通类来说,不要定义虚析构函数,会增加虚函数表的内存开销;
4、赋值构造函数的问题:简单来说,如果在你的类里有分配内存的指针数据,就需要赋值构造函数,如果没有,就不需要,因为这涉及到一个深拷贝和浅拷贝的问题,在函数析构的时候会导致释放报错。如果没有显示定义一个赋值构造函数,系统会默认一个public的赋值构造函数,如果我们想禁掉这个公共接口,我们可以显示的申明为private;
5、对于成员函为保证数据的安全,要加const修饰;
二、代理类
代理类的作用就是将类型不同的但是具有相关联的对象以容器的形式(例如: A a[100])进行管理。
1:问题的提出
class A
class B:public A
class C:public B
考虑下面的实现
{
A a[100];
B b;
A[i++] = b;
}
有两个问题:第一个就是作为基类A,如果类里面有纯虚函数的话,这种申明是不合法的,因为含有纯虚函数的类是不能够实例化的,第二个问题就是赋值,仅仅将B类里面和A类里面共有的数据进行了赋值,对于B有A无的数据,赋值操作将会丢失这部分信息。
2:解决方法一
采用指针的方法来解决
{
A *a[100];
B b;
A[i++] = &b;
}
采用这样一种方法虽然能够解决上述两类问题,但是又会产生一个新问题,因为对于B对象,如果是局部变量,随着它的生命周期的结束,对象销毁后,指针的指向就无法确定了
3:解决方法二:
采用指针加副本的形式
{
A *a[100];
B b;
A[i++] = new B(b);
}
采用这样一种方法解决了上述问题,但是同样又会产生新的问题:
当我们需要用a[i]指向一个新建的对象B,而这个对象正好是a[j],
如果我们采用如下形式:a[i] = a[j]的话。就会出现解决方法一所产生的问题;
如果采用a[i] = new A(a[j]);就会产生一开始部分复制的问题;
4、代理类的解决
首先定义虚复制函数;如下:
class A
{
......
virtual A* Copy() const= 0;
}
class B:public A
{
......
virtual A* Copy() { return B(*this);}
}
class C:public B
{
......
virtual A* Copy() { return C(*this);}
}
然后定义个代理类
class D
{
......
D& operator=(A* a);
private:
A* m_A;
}
D& D::operator=(A* a)
{
m_A = a.Copy();
}
这样就可以解决问题了:如下:
D d[100];
B b;
d[i] = b;
另外一篇文章:
问题:怎样设计一个c++容器,使它有能力包含类型不同而彼此相关的对象?
假设有一个表示不同种类的交通工具的类派生层次:
class Vehicle
{
public:
virtual double weight() const = 0;
virtual void start() = 0;
//.....
};
class RoadVehicle:public Vehicle {/*...*/};
class AutoVehicle:public RoadVehicle{/*....*/};
为了表述简单,使用数组来实现。由浅入深的提出以下几个方案:
方案一: 数组直接存储对象
Vehicle parking_lot[1000];
AutoVehicle x = /*.....*/
parking_lot[num_vehicles++] = x;
这里出现两个问题:
(1) Vehicle 为抽象基类,类Vehicle本身不会有对象,因此定义对象数组也就不可行了。
(2) 即使去掉类Vehicle中的纯虚函数,parking_lot 保存的是派生类裁剪后的对象,而不是所有继承自Vehicle类的对象的集合。
方案二:数组存储指针
Vehicle* parking_lot[1000];
AutoVehicle x = /*...*/;
parking_lot[num_vehicles++] = &x;
这里出现下面的问题:
我们存储在parking_lot中的是指向x的指针,如果x是个局部变量,一但变量x没有了,parking_lot就不知道指向什么东西了。我们可以改进一下,放入parking_lot中的值,不是指向原对象的指针,而是指向它们的副本的指针。在释放数组的时候,也释放其中所指向的全部对象。上面的代码可以改写为:
AutoVehicle x = /*...*/;
parking_lot[num_vehicles++] = AutoVehicle(x);
但是这样改也带来了动态内存管理的负担。另外,只有当我们知道要放到parking_lot中的对象的静态类型后,这种方法才能起作用。为了解决这个问题可以使用用虚复制函数, c++中处理未知类型的对象的方法就是使用虚函数。我们可以在类Vehicle中添加纯虚函数: virtual Vehicle* copy() const = 0; 则在派生类AutoVehicle 中 copy() 函数可以如下实现:
Vehicle* AutoVehicle::copy() const
{
return new AutoVehicle(*this);
}
然后就可以这样存储了: parking_lot[num_vehicles++] = x->copy(); 这里可以在运行时确定所要调用的copy函数。
方案三:定义代理类
方案二已经能较好的解决问题了,但是我们想避免显示的处理内存分配,又能保持类Vehicle在运行时绑定。这里采用代理类技术,就是定义一个行为和Vehicle对象相似,而又潜在地表示所有继承自Vehicle类的对象的东西。每个Vehicle代理都代表某个继承自Vehicle类的对象。只要该代理关联着这个对象,该对象就肯定存在。
问题引入
假设有一个表示不同种类的交通工具的类派生层次:
其中,Vehicle是一个抽象基类。在实际中,我们可能会使用某种容器类,比如数组:
上述定义没有产生预期的效果,为什么?由于Vehicle本身不会有对象,也就不可能有其对象数组了。
如果我们去掉类Vehicle的所有纯虚函数,写出类似于下面的语句,会有什么效果呢?
答案是:把x赋给parking_lot的元素,会把x转换成一个Vehicle对象,同时会丢失所有在Vehicle类中没有的成员。该赋值语句还会把这个剪裁了的对象复制到parking_lot数组中。
这样,我们只能说parking_lot是Vehicles的集合,而不是所有继承自Vehicle的对象的集合。
使用代理类
有没有一种方法既能让我们避免显示地处理内存分配,又能保持类Vehicle运行时绑定的属性呢?
有!方法是:定义一个行为和Vehicle对象类似,而又潜在地表示了所有继承自Vehicle类的对象的东西。我们把这种类的对象叫做代理(surrogate)。
即:为每个对象创建一个代理,并要将代理存储在容器中.创建代理就会复制所代理的对象.
我们再定义两个Vehicle的子类Car和Truck:
那么我们就能够实现继承自Vehicle的类的对象的容器:
结果为:
start car
start truck
start car
start truck
start car
start truck
start car
start truck
start car
start truck
每个Vehicle代理都代表某个继承自Vehicle类的对象。只要该代理关联着这个对象,该对象就肯定存在。
因此,拷贝代理就会拷贝相应的对象,而给代理赋新值也会先删除旧对象,再拷贝新对象(改变代理类实际关联的那个对象的类型)。
所幸的是,我们在类Vehicle中已有虚拷贝函数copy来完成这些拷贝工作。
总结
将继承和容器共用,迫使我们要处理两个问题:控制内存分配和把不同类型的对象放入同一容器中。采用基础的C++技术,用类来表示概念,我们可以同时兼顾这两个问题。我们提出一个名叫代理类的类,这个类的每一个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的任何类的对象。通过在容器中用代理对象而不是对象本身的方式,解决了我们的问题。
代理类缺点:即每次使用都得要进行复制,如果对于某些特别大的类来说,复制并不是一个明智的选择.