条款30:GotW#28 “Fast Pimpl”技术(The “Fast Pimpl” Idiom)

如下采用了一些称为“降低依赖”或“效率”方面的捷径,在很多时候颇有吸引力,但它不是好主意。有一个极好的习惯可以成功同时实现这两个目标:

【问题】

标准的malloc()个new()调用的开销很大,在下面的代码,程序员最初在class Y中设计了一个类型X的成员:

//尝试#1
//文件y.h
#include "x.h"
class Y
{
	/*...*/
	X x_;
};

//文件y.cpp
Y::Y(){}

这个Y的声明要求X声明可见,为了避免这个问题做了第二次尝试:

//尝试#2
//文件y.h
class X;
class Y
{
	/*...*/
	X* px_;
};

//文件y.cpp
#include "x.h"
Y::Y():px_(new X){}
Y::~Y(){delete px_;px_ = 0;}

这很好的因此了X,但它造成了当Y被广泛使用的时候,动态内存分配开销使性能降低。

最后程序员发现“完美”解决方案:既不需要在y.h中包含x.h,也不需要动态内存分配(甚至前置声明都不需要):

//尝试#3
//文件y.h
class Y
{
	/*...*/
	static const size_t sizeofx = /*某个值*/;
	char x_[sizeofx];
	
};

//文件y.cpp
#include "x.h"
Y::Y()
{
	assert(sizeofx >= sizeof(X));
	new(&x_[0])X;

}
Y::~Y()
{
	(reinterpret_cast<X*>(&x_[0]))->~X();
}

【解答】:

不要这么做。底线:C++不直接支持不定类型,这是依赖于这一局限上的脆弱尝试。

为什么尝试#3是糟糕的:

1.对齐。不像::operator new()得到 的动态内存,这个x_的字符并不确保满足类型X的对齐要求。试图让x_工作更可靠,程序员需要使用一个"max_align"联合:

union max_align
{
	short dummy0;
	long dummy1;
	double dummy2;
	long double dummy3;
	void*	dummy4;
	/*...*/
};

union
{
	max_align m;
	char x_[sizeofx];
};

这不确保完全可移植性,已经足够完美,只是极少数系统上,它不能如愿工作。

2.脆弱。Y的作者必须小心处理Y的其他普通函数。例如,Y决不能使用默认赋值操作,必须禁止或自己写一个版本。

3.维护代价。当sizeof(X)超过sizeofx,程序员必须增大sozeofx,选择一个较大的sizeofx,但换来效率降低。

4.低效率。只要sizeofx大于sizeof(X),空间就被浪费了,这个损失可以降低最小,但又造成维护代价。

更好的解决方法:

隐藏X的动机是避免Y的用户知道X。为了消除这种方法,通常使用“pimpl”方法。唯一的问题是,“pimpl”方法由于X的对象在自由空间分配内存而导致性能下降。通常采用重载的operator new固定长度的内存分配器可以比通用的内存分配器性能高的多。

不幸的是:这意味Y的作者必须也是X的作者。通常,这是不成立的:

//文件y.h
class YImpl;
class Y
{
	/*...*/
	YImpl* pimpl_;
};

//文件y.cpp
#include "x.h"
struct YImpl
{
	/*私有成员*/
	void* operator new(size_t);
	void operator delete(void*);
};
Y::Y():px_(new YImpl){}
Y::~Y(){delete pimpl_;pimpl_ = 0;}

上述是所谓的“Fast Pimpl”。但是考虑下Fast Pimpl的代价。

如下是讨论其可用性:一个技巧,将它们放入一个通用尺寸内存分配器模板技术:

template<size_t S>
class FixedAllocator
{
public:
	void* Allocator(/*请求的大小总是S*/);
	void Deallocate(void*);
private:
	/*用静态成员实现*/
};

因为它的私有细节很可能使用static实现,问题是Deallocate()是否被一个static对象的析构函数调用会有问题。这里可以使用单例模式比较替代比较安全。

template<size_t S>
class FixedAllocator
{
public:
	static FixedAllocator* Instance();
	void* Allocator(/*请求的大小总是S*/);
	void Deallocate(void*);
private:
	/*单例来实现*/
};

//辅助类
struct FastPimpl
{
	void* operator new(size_t s)
	{
		return FixedAllocator::Instance()->Allocator(s);
	}
	void operator delete(void* p)
	{
		FixedAllocator::Instance->Deallocate(p);
	}
};

//现在你很容易实现任意的fast pimpl:
struct YImpl : FastPimpl
{
	/*私有成员*/
};

但是小心:这虽然很好,但是也别乱用Fast Pimpl。你得到了最佳内存分配,但是别忘了代价:维护这些独立的链表将导致空间效率下降,因为这比通常情况下更多的内存碎片。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值