技术(二):在簇中分配对象

C++程序通常要为一整组对象分配内存,随后将其同时释放

  • 定义一个包含一整组对象的集合的类
  • 让一个集合包含不相关的类的对象的一个好方法:多重继承

  • 问题:交互式或长时间运行的程序中常有一堆各式各样的C++对象需要一块释放

设计方案

  • 使用类来表示概念:需要一个类表示“一些要一起释放的对象”
1
2
3
class Cluster {
// ...
};
  • Cluster类的对象c必须明显包含将在某个适当的时候被释放的其他对象

  • 必须有一种方法分配一个对象到某个特定的Cluster里

    • T* tp = new(c) T;
  • 类型T必须有一个成员

    • void* T::operator new(size_t, Cluster&);
    • 对类型T的调整限制了Cluster的用途
  • 要能将任何自定义类的对象放入簇中

    • 避免修改已经存在了的T的定义
  • 使用继承和一个间接层(indirection)

    • 使用多重继承:由基类ClusterItem派生出的类的对象可以分配在簇中
1
2
3
4
5
class ClusterItem {
public:
void* operator new(size_t, Cluster&);
// ...
};
  • 有些C++实现限定只要有operator new成员函数就必须保证有一个只有一个参数的类型的operator new
    • 保证该类的对象可以用普通的new表达式分配
    • 如果不希望这样做:将普通的operator new私有化
1
2
3
4
5
6
7
8
9
10
class ClusterItem {
public:
void* operator new(size_t, Cluster&);
private:
void* operator new(size_t);
ClusterItem(const ClusterItem&);
ClusterItem& operator=(const ClusterItem&);
// ...
};
  • 缺省构造函数
  • 虚析构函数
  • ClusterItem对象的销毁仅伴随Cluster对象的销毁
    • 不公有化ClusterItem的析构函数
    • Cluster作为一个友元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ClusterItem {
friend class Cluster;
public:
void* operator new(size_t, Cluster&);
ClusterItem();
protected:
virtual ~ClusterItem() { }
private:
void* operator new(size_t);
ClusterItem(const ClusterItem&);
ClusterItem& operator=(const ClusterItem&);
// ...
};
  • Cluster类需要构造函数、析构函数
    • 将ClusterItem作为友元
    • 限定Cluster对象的复制操作:不考虑该操作的含义
1
2
3
4
5
6
7
8
9
10
11
class Cluster {
friend class ClusterItem;
public:
Cluster();
~Cluster();
private:
Cluster(const Cluster&);
Cluster& operator=(const Cluster&);
};

实现

  • 写一个容器
    • Cluster中保存一个ClusterItem的链表
    • 会按与创建是相反的顺序删除ClusterItem:局部变量分配的镜像(回收策略)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Cluster {
friend class ClusterItem;
public:
ClusterItem* head;
Cluster();
~Cluster();
private:
Cluster(const Cluster&);
Cluster& operator=(const Cluster&);
};
class ClusterItem {
friend class Cluster;
public:
void* operator new(size_t, Cluster&);
ClusterItem();
protected:
virtual ~ClusterItem() { }
private:
ClusterItem* next;
void* operator new(size_t);
ClusterItem(const ClusterItem&);
ClusterItem& operator=(const ClusterItem&);
};
Cluster::Cluster(): head(0) { }
Cluster::~Cluster() {
while (head) {
ClusterItem* next = head->next;
delete head;
head = next;
}
}
  • 类ClusterItem中的虚析构函数可以删除用户任何从ClusterItem派生出来的类的对象

  • 使用 new(c) T; 分配某个继承自类ClusterItem的类T的对象时,会把适当的Cluster作为成员ClusterItem::operator new的参数

    • 唯一判断Cluster是否有效的时机
  • operator new的任务是分配裸内存(raw memory)而不是处理已经构造好的对象

    • 将ClusterItem链接到Cluster链中的工作应该由ClusterItem的构造函数完成
  • operator new要在构造函数能看到的地方保存Cluster

    • (源文件范围内可见的)静态变量 static Cluster* cp;
    • 只在成员函数定义的文件中有效,不必担心名字问题
1
2
3
4
5
6
7
8
9
10
11
void* ClusterItem::operator new(size_t n, Cluster& c) {
cp = &c;
return ::operator new(n);
}
CluterItem::ClusterItem() {
assert(cp != 0);
next = cp->head;
cp->head = this;
cp = 0;
}
  • 定义声明了的缺省的operator new,即使不使用(一些C++实现的要求)
1
2
3
4
void* ClusterItem::operator new(size_t) {
abort();
return 0;
}

加入继承

  • 使用这些类的方法是从ClusterItem派生新类
1
2
3
4
5
6
7
8
9
10
class MyClass : public ClusterItem {
// ...
};
int main() {
Cluster c;
MyClass* p = new(c) MyClass;
// ...
}
  • 两个问题
    • 对某些未定义的类进行簇式分配
    • ClusterItem的析构函数私有,不能声明任何ClusterItem派生类局部变量
1
2
3
4
5
6
7
8
9
10
11
12
class MyClass {
// ...
};
class MyClusteredClass: public MyClass, public ClusterItem { };
int main() {
Cluster c;
MyClass* p = new(c) MyClusteredClass;
// ...
}
  • new表达式分配一个类型为MyClusteredClass的对象
    • 将该对象的地址转换成类型MyClass*
    • 只要不试图删除这个指针,可像任何其他MyClass指针一样使用
  • MyClusteredClass需再定义缺省构造函数等

Summary

问题解决的关键在于实现之前正确阐明问题


《C++沉思录(Cplusplus Thinking)》笔记