2008年9月26日星期五

C++中控制对象的创建

在C++中,可以通过很多方法来定义对象的创建方式和地点。

1. 将构造函数设为私用,可以阻止直接创建。
这个方法是最常见的,恐怕我们最熟悉的就是单件模式(Singleton)。通过类的静态方法来创建对象,以达到控制对象个数的目的。
实际上,这种情况下也就直接阻止了派生。因为C++要求派生类要显示或隐式调用基类的构造函数。但是,真的没法派生了?当你要为这个类增加功能,又不想破坏现有的代码时,你会怎么做呢?
看看下面的代码:


class B{
friend D;
B(){};
}

class D{
public:
D();
D(int);
}

D d;

class DD:public D{}; // error, DD::DD() cannot access

是的,可以通过友元来获取访问权限,这样就可以在原来基类的基础上扩充功能了。而且这个派生类仍旧延续了之前的语义,无法被继承,因为DD无法访问在最上层的B::B()。

2. 利用私有的析构函数阻止派生,堆栈和全局分配。
析构函数声明为私用就可以避免堆栈和全局分配。这样做还能防止随便使用Delete,这种类的典型使用需要一个高度优化的自由存储分配系统。

析构函数声明为私用也可以用来阻止派生。这时候,构造函数最好也设计成私有,这样可以保证创建和销毁的语义相同。当然,取巧的方法也有,只要简单的重载delete操作符即可。但是这样恐怕只能通过编译,仍然无法访问基类的析构函数,因此可能会造成内存泄漏。

3.阻止复制的语义
把复制构造函数设为私用就可以防止复制构造,同理可以用在赋值操作符上面。按照Effective C++的建议,当显式提供了复制构造函数,最好也要提供赋值操作符,同时,他们两个在复制对象内容上有一致的语义。

4.用Placement new指定对象的内存地址
Placement new语义是用户自己指定一块内存,用来存放类的对象。
一般使用new来创建对象,对象的存放位置无法控制,只能由系统来确定。
而Placement new却可以控制对象的存放位置。

Class X{
//....
}

void * pv = malloc(sizeof(X) *1000);
X* pX = new(pv) X();

事实上这和一般的new区别不大。一般的new是先申请一块空间,大小为sizeof(X)+delta。(delta是cookie的大小。cookie是用来记录在自由存储区请求内存大小及其他信息的一个系统实现,隐藏在应用程序背后,我们看不到,也不需要管,在window和unix上cookie的大小略有不同。)然后再把这块空间的首地址转换成X*,接着在上面调用X的构造函数,填充这块区域,完成初始化。

Placement new只不过是自己决定了空间的大小,以及确定了起始位置。Placement new主要用于批量构造对象,比如1000个X连续排在一起。这样一方面减少了cookie数量(从1000->1),也能够使访问更迅速(在一块连续的存储上)。

但是注意使用placement new时可能会造成内存泄漏,也就是在Placement new所返回的内存中,如果有指针指向其他的自由内存的话,当调用构造函数之后,很可能把该指针覆盖掉,这样那块自由内存就“丢失”了。

没有评论: