设计实现C++内存的半自动释放(二)

2014-11-24 09:25:38 · 作者: · 浏览: 2
hildern.begin(), this_parent->childern.end(), this); this_parent->childern.erase(it); // this_parent->childern.remove(this); } //插入当前对象到parent对象的子对象列表中 _appendObjectList(parent); }

三、代码分析 1、为什么构造函数的访问权限都是protected 从上面的代码中,我们可以看到类Object的构造函数是protected的,为什么不是public而是protected的呢?因为我不知道如果不用它作为基类,则实例化一个这样的类有什么意义,所以我把它声明为protected。也就是说,它只在在实例化其子类对象时,才能被调用实例化基类的部分。
2、如何实现复制构造函数 如何实现复制构造函数是一个我想了很久的问题,这个实现的难点是究竟要不要复制子对象列表,从上面的代码中,可以看出我并没有去复制子对象列表。为什么呢?因为我觉得子对象列表是一个对象自己私有的部分(不是指访问权限),其他对象在根据当前对象复制一个对象,根本没有必要去复制它的子对象列表。从另一个角度来解释,就是子对象列表childern是用来管理内存,释放其自身new出来的对象的,并不是用来记录该对象的有用的信息的,所以不必要复制子对象列表。再者,我希望这个基类Object的存在,对我们平时类的编写的影响减少到最低,就像类Object好像并不存在一样,例如,如果没有Object,我们定义一个类A,当我们实现其复制构造函数时,只会关心类A的成员;而当类A是继承于Object时,我希望还应该如此,类A在实现自己的复制构造函数时,应该只关心类A的成员变量,而不关心别的类的成员。
3、如何定义赋值操作函数 基于上述实现复制构造函数的思想,这里的实现与上面的实现差不多,只不过复制构造函数是用来创建对象的,而赋值操作函数是对一个已存在的对象的状态进行修改。所以同样地,我也没有复制其子对象列表。而对一个已有父对象的对象改写其父对象并没有多大的意义,所以为了管理的方便,在调用赋值操作函数时,如果当前对象没有设置父对象,则把当前对象的父对象设置为右值对象的父对象,若当前对象已经有父对象,则什么也不做。
4、创建销毁对象的三种广式是否都应用 我们可用使用三种方式来创建对象,那么是否这三种对象都没有问题呢?现在我们假设类A、B继承于类Object,在类B中创建类A的对象,则创建类A的方式有三种,如下: A a(this); A *p = new A(this); A a1(a); 其实这三种方式都是没有问题的,他们都会调用自己析构函数,进行相同的操作,只是调用的时机不同而已。
第一种情况,对象a存在于栈内存中,当类B的对象b还未销毁时,它已经被释放掉,从b的子对象列表中删除,所以不会存在delete一个栈中的对象的情况。
第二种情况,指针p指向的对象,会在b被销毁调用其析构函数时,从其子对象列表中找到其地址,并被delete。对于这种情况,如果我们主动地使用delete,如delete p;则会不会有问题呢?答案是没有问题的。因为无论是自己使用delete还是在父对象销毁析构时对其子对象列表中的所有对象进行delete,都是一样的,只是调用的时间不同而已,其操作是一样的。
第三种情况,与第一种情况相同,只是创建对象的方式不一样而已。
注:我们可以看到在析构函数中的while循环中,只有delete,没有从childern中删除结点,而且每次delete的都是childern中的第1个结点,这样是否有问题呢?当然是没有问题的。因为当delete一个子对象列表中的对象时,因为其也是类Object的子类对象,所以会去调用子对象的析构函数,从而又会去调用Object的析构函数,把子对象从其父对象的子对象列表中删除。所以真正的删除结点的操作是在子对象中完成的,而不是在父对象中。
在写代码和 阅读代码时,一定要搞清楚,哪些操作是当前对象的操作,哪些对象是其父对象的操作,不然思路会很混乱。
5、使用方式 使用方式非常简单,只要把Object或其子类作为你要定义的类的基类,在类中new对象时,把当前类的地址(this)或其他Object类的子类地址作为参数,传入到构造函数中即可。若不传入参数,则默认没有父对象,则不能使用自动释放内存的功能。示例代码如下:
#include 
  
   
#include Object.h
using namespace std;
class Student:public Object
{
    public:
        Student(Object * parent = NULL)
            :Object(parent){++new_count;}
        ~Student()
        {
            ++delete_count;
        }
        static int new_count;
        static int delete_count;
    private:
        int stu_id;
};
int Student::new_count = 0;
int Student::delete_count = 0;
class Teacher:public Object
{
    public:
        void createStudent()
        {
            for(int i = 0; i < 10; ++i)
                new Student(this);
        }
    private:
};
int main()
{
    {
        Teacher t;
        t.createStudent();
    }
    cout << Student::new_count<
   
     运行结果如下: 
    \ 
    \ 从运行的结果来看,Student类被实例化了10次,其析构函数也被调用了10次,也就是说实现了内存自动释放的功能。而在我们的测试代码中,我们是没有delete过任何对象的,我们只是new了10个Student对象,并把当前对象(t)的地址传给Student的对象,作为其父对象。main函数中的花括号的作用,只是为了让Teacher类的实例t出了作用域,运行其析构函数,方便我们查看结果而已。 四、性能分析 因为我打算把这个类作为所有类的基类,所以这个类的内存管理的效率是什么重要的,因为它将影响整个程序的效率,所以我对其进行了测试,在newStudent中创建1024*1024个对象,然后释放,其平均时间为1.1秒左右。可见其效率还是相当高的。 我们知道list有一个成员函数remove,它的原型如下:
    
void remove (const value_type& val);
为什么我在实现中没有使用这个方便的函数,而使用泛型算法find,查找到该对象的指针所在链表中的迭代器,再使用list的成员函数earse呢?我们可以先来看看remove的说明,如下: Removes from the container all the elements that compare equal to val. This calls the destructor of these objects and reduces the container size by the number of elements removed. 它的意思就是说,遍历整个容器,找出与val相等的结点,并删除。所以调用remove总是遍历整个链表。而从我们的使用中,我们可以知道,子对象列表中的值都是不一样的,这样总是遍历整个链表并没有意义,而且非常耗时,所以,我改成了上面代码所写的那样,使用泛型find和list的成员函数earse。find的操作是,找到第一个与val