这几天在看《C++沉思录》,看到第六章Handle类时,有一些疑惑,最近才慢慢想通,故有了本篇博客,作为一个记录。
首先说到代理类的产生:
class B{
public:
//XXX; virtual function = 0;
};
class D : public B{
public:
//XXX virtual function;
};
class DD : public B{
public:
//XXX virtual function
};
那么,我们想使用一个数组来保存 B类对象及其子类该如何呢?
B bs[100]; 这样子肯定是不可以的:
1 B作为一个含有pure virutal function的类,是不能实例化的,更谈不上有数组产生。
2 即使语法上允许(将pure virtual 变为virtual),也无法保存D/DD对象,因为会发生对象切割。(保存进去,全部变成了B对象)
于是问题来了,那该如何呢:
B* bs[100];呢,怎么样,嗯,可以,指针/引用是可以实现多态化的。
p.s. 书上这里(反)例是:
D d;
B* bs[100];
int index= 0;
bs[index++] = &d;
这种是不可以的,因为d作为一个本地变量,超出作用域后,那么bs[0]指的是什么?
在这里,个人一直疑惑,傻啊,既然能产生局部变量,我自己不可以new 吗?
bs[index++] = new D/DD;
这样不存在作用域问题了吧。到时候统一释放,嗯。想的很美好。
bs[0]什么时候释放合适呢?释放的早了,可能导致其他用到bs【0】的地方出现错误,释放的太晚,本可以早早释放的内存又被占用着。最严重的是最后可能都没有释放。
于是这样的一种技术产生了(很难认为是一个技术,更像一个小技巧)
class TAgent{
public:
~TAgent(){
delete m_lpt;
}
TAgent():m_lpt(nullptr){} //允许产生TAgent数组
TAgent(const TAgent& t){
//is.m_lpt = new //要实现深拷贝,可是由于T中存在一个继承体系,此处,new什么类型呢?
//这里,是忽然间写的过程中想到的,个人觉得比书中的例子好。书中是是要实现对数组中的某一个对象的拷贝,所以需要动态类型
//此处,却必须实现深拷贝,由于有指针成员的存在,所以,重新分配内存(值语意)是合适的。
if(t.m_lpt == nullptr){throw "empty m_lpt";}
this.m_lpt = t.m_lpt->copy(); //给T添加一个virtual的copy函数,各个子类完成对自身的一个copy操作
}
TAgent& operator=(const TAgent& t){ //值语意
if(t.m_lpt == nullptr){throw "empty m_lpt";}
this.m_lpt = t.m_lpt->py();
}
TAgent(const B& b): m_lpt(&b){}
//XXX T中的函数实现->此处不用设为virtual函数,内部实现是直接使用m_lpt进行转发。(当然咯,应该判断下m_lpt是否为nullptr - - )
private:
T* m_lpt;
};
ok,“不明不白”多出了一个TAgent类。繁琐死了,你说。TAgent类,代理类,可是我没有想明白原因啊,为什么需要这个类?---一开始学习,我也很郁闷,太麻烦- -
不过现在,你再回头看看之前的问题,我们new D/DD,是否要进行内存管理,何时释放?类似问题。而此时呢?
TAgent t[100];
D d;
DD dd;
t[0] = d;
t[1] = dd;
.....
完工,不用担心何时释放的问题了,t超出作用域后,析构函数会自动释放各自的成员指针。为了这个好处,我们多了一个Agent我觉得还是很值得的。同时,也隐藏了继承体系的存在(不过在这里,如果不是继承体系,我在设计时就要想想,是否这样做值得- - )
p.s. 写到这里,有些东西忽然间更加通顺了(还是要多动手写写博客,写写代码。写的过程中,往往都通了- -),比如:可以这样写吗:
TAgent t[100];
t[0] = new D;
t[1] = new DD;
...
当时看书的过程中,还一直在这样想。现在发现可以吗?不可以,TAgent 中的operator=我们已经看到了内部使用的是一个copy副本,因为我们要实现的是值语意(大多数情况下都是这种符合情况),如果在此处new,那么除非是调用者自己delete?哦,no,你没办法做到,在operator=内部去delete也不行,因为你无法判断出传入的对象时堆上的(new)还是栈上的。所以呢,此处还是得有点“契约式设计”的意味了。而约定好全部传入的是堆上的,那么 m_lps = obj.m_lps->copy(); delete obj.m_lps;也略为复杂了。(其实还有一个原因就是。。。t[0] = new D;在这里是不对的。因为operator=接受的是一个引用,而不是指针,而如果t[0] = *(new D);我想这样写就有点脑残了- - 。
写到这里,我们发现了一个问题,就是每次都需要一个copy副本,那么这是有点不爽快的。给TAgent数组里面放入一个对象就需要构造出两个对象,一个局部对象,一个堆上的副本。这不爽。能否去掉这个copy呢,于是有了一个Handle(句柄类。不要和windows编程里的句柄搞混了)技术。其实内部使用了引用计数机制来实现。这个在下一篇中我会尽量写清楚,让思维一步一走。
p.s. 在看书的过程中,经常我们发现的困惑是,我们能看懂别人怎么做,但是就是很难理解为什么要这么做?如果你学习设计模式,一开始就会觉得“简单问题复杂化”,感觉不到好处,或者觉得好处微乎其微。最后才会知道好处- - 。(当然别误用,搞过度设计)
由于是类似做记录,我个人是按照个人理解和思维一步步在博客上写,上面代码没有在编译器中跑过,但是大体思想上应该是没什么错误的。如果有什么错误的理解或者不对的地方,恳请提出,谢谢。