在簇中分配对象

问题

假设我们有一些需要一起释放的对象,这些对象的类型并不完全一致.我们需要一种方式跟踪这些对象并且在合适的时候一起释放他们.

设计方案

按照C++的传统艺能:用类来表示概念,我们需要定义一个类,表示一些要一起释放的对象.假设叫做Cluster

class Cluster {};

我们知道这个类应该包含一些对象,并且在适当的时候释放掉他们.适当的时候应该怎么定义呢?一般而言,在析构Cluster对象的时候应该一并释放掉包含在Cluster对象中的其他对象.因为错过这个时机,我们将不知道什么时候释放这些对象了.
对于这些要释放的对象,我们有什么了解呢? 因为Cluster对象包含这些对象,所以我们应该有一种方式将这些对象创建在Cluster对象里面.也就是说:我们需要在簇c中分配一个类型为T的对象.
T* p = new(c)T;
这个方案需要类型T支持void* operator new(size_t, Cluster&).但是并不是所有的类都支持这个运算符.考虑我们前面提到的代理类和句柄类的方案,我们可以通过继承和指针创建一个包含任意类对象的类.假设我们定义基类为ClusterItem:

class ClusterItem {
public:
    void* operator new(size_t, Cluster& c);
};

一般C++编译器要求我们如果重载了operator new那么最少应该包含一个void* operator new(size_t);的重载.因为我们不想在Cluster以外的地方分配ClusterItem的对象,所以我们将它设置为delete.同理:我们禁用掉拷贝构造和赋值运算符.
因为我们定义的是一个基类,所以应该包含一个虚析构函数.我们还需要提供默认构造函数,以便可以定义这个类型的数组对象.
现在我们得到了ClusterItem的声明

class ClusterItem {
    friend class Cluster; // 为了访问析构函数
public:
    ClusterItem();
    void* operator new(size_t, Cluster& c); // 使用placement_new的方式
    ClusterItem(const ClusterItem&) = delete;
    ClusterItem& operator=(const ClusterItem& ) = delete;
    void* operator new(size_t) = delete; // 我们不实现这个,也不允许使用
protected:
    virtual ~ClusterItem() {}
private:
    // ...
};

因为我们希望只有Cluster可以销毁ClusterItem对象,所以我们不将析构函数设置为public,同时设置友元类Cluster.
现在我们来看看Cluster类.对于这个类我们知道应该有public的构造函数,析构函数,构造函数应该有默认构造函数.同时,Cluster应该持有ClusterItem的一系列对象,用于在适当的时候进行销毁.我们采用最简单的链表形式持有ClusterItem对象.由此得到Cluster的定义:

class ClusterItem {
    friend class Cluster; // 为了访问析构函数
public:
    ClusterItem();
    void* operator new(size_t, Cluster& c); // 使用placement_new的方式
    ClusterItem(const ClusterItem&) = delete;
    ClusterItem& operator=(const ClusterItem& ) = delete;
    void* operator new(size_t) = delete; // 我们不实现这个,也不允许使用
protected:
    virtual ~ClusterItem() {}
private:
    ClusterItem* next; // 新增
};

class Cluster {
public:
    Cluster():head(nullptr) {}
    ~Cluster() {
        while (head != nullptr) {
            ClusterItem* next = head->next;
            delete head;
            head = next;
        }
    }
    Cluster(const Cluster&) = delete;
    Cluster& operator=(const Cluster&) = delete;
private:
    ClusterItem* head;
};

现在我们来看看ClusterItem的实现.由于我们使用了placement_new的语义重载了operator new,但是operator new仅仅是申请void * 类型的内存,并不能直接操作对象,所以将ClusterItem加入Cluster持有的链表的动作应该交给ClusterItem的构造函数才是正确的.但是只有operator new的重载才有可能获取到一个有效的Cluster对象,构造函数如何实现呢?
如果给ClusterItem添加一个构造函数ClusterItem(Cluster& );是不是就可以了?这样我们要考虑默认构造函数应该怎么实现.默认构造函数无法直接将自己添加到Cluster持有的链表中,需要Cluster提供一个添加的方法才行.
这里我们采用书中使用的比较巧妙的方式实现.
我们在实现的文件中添加一个static类型的变量:static Cluster* cp;因为使用了static修饰,所以他的作用域不会超过这个文件.然后我们实现这个类:

void* ClusterItem::operator new(size_t n, Cluster& c) {
    cp = &c;
    return ::operator new(n); // 使用全局命名空间的operator new 申请空间
}

// 构造函数中将自身加入链表中.
ClusterItem::ClusterItem(){
    next = cp->head;
    cp->head = this;
}

加入继承

我们定义的ClusterItem是一个需要被继承的基类,我们来看看怎么使用它.

class MyClass : public ClusterItem {};
int main() {
    Cluster c;
    MyClass* p = new(c) MyClass;
    // ...
}

因为我们限制了ClusterItem的析构函数是protected访问权限,这导致我们不能定义MyClass my_class;的局部变量.通过引入多继承可以解决这个问题.

class MyClass {};

class MyClusteredClass : public MyClass, public ClusterItem {};

int main() {
    MyClass my_class; // 这里是合法的
    Cluster c;
    MyClass *mp = new(c) MyClusteredClass; 
    // ...
}

这里通过多继承将mp添加到c的管理中,当c析构的时候,通过ClusterItem的虚析构函数会正确的释放掉MyClusterdClass进而正确释放MyClass.

你可能感兴趣的:(在簇中分配对象)