当前位置:Linux教程 - Linux - 模拟虚构造函数的内存分配优化

模拟虚构造函数的内存分配优化

//转贴自我的朋友云风的一篇文章,
//里面有些DarkSpy自己写的注释,希望能给不太懂这篇文章意思的朋友一些提示。


构造函数不能是虚的, 这让人很郁闷.
在 Thinking in C++ 第2版的最后作者给出了一种模拟虚构造
函数的方法, 基本是这样的.


代码:--------------------------------------------------------------------------------
// 给出一个抽象类 shape, 里面有要提供的接口
class shape {
public:
shape();
virtual ~shape();
virtual void draw();
//....
};

// 别的类用这个派生

class circle : public shape{
public:
circle();
~circle();
void draw();
//...
};

class rectangle : public shape {
public:
rectangle();
~rectangle();
void draw();
//...
};

// 再给一个 shapewrap 封装一下
class shapewarp {
protected:
shape *object;
public:
shapewrap(const string &type) {
if (type==""circle"") object=new circle;
else if (type==""rectangle"") object=new rectangle;
else {
// ...
}
}
~shapewrap() { delete object; }
void draw() { object->draw(); }
};
--------------------------------------------------------------------------------

我昨天在做脚本的参数分析的时候, 想给出一个类似 vb 或者 java
里那样的 var 类型, 能够装下所有不同种类的变量.
基本上的要求更上面的例子很像. 但是出于效率的角度,
考虑到 wrap 类仅仅只有 4 字节, 放了一个对象指针.
无论在何地构造出 wrap 对象, 都会有一个动态的 new 操作
做内存分配, 如果参数表用 stl 的容器装起来, 这些 new 操作
做的内存分配也无法用到 stl 容器的比较高效的内存管理策略.
这让人心里很不舒服, 所以就着手优化这一部分的代码.

开始的核心思想是能够对小尺寸对象不做 2 次内存分配.
解决方案是在 warp 对象里预留一小块空间安置小对象用.

基类和 warp 类就是这样设计的.

代码:--------------------------------------------------------------------------------
class var;

// 基类是一个为空的东西
class var_null {
public:
typedef int var_type;
enum { type=''null'' }; // 类型识别用, 每种类型用一个整数表示
var_null() {}
virtual ~var_null() {}
void *operator new ( size_t size , var *p);
void operator delete (void *p, var *v) {}
void *operator new ( size_t size) { return ::operator new(size); }
void operator delete (void *p) { ::operator delete(p); }
protected:
virtual void clone(var *p) const { new(p)var_null; }
void copy_to(var *p) const;
bool is_type(var_type type) const { return get_type()==type; }
virtual var_type get_type() const { return type; }
private:
virtual void do_copy_to(var_null &des) const {}
friend class var;
};

// 给出一个 null 是空对象
extern var_null null;

// warp 类
class var {
public:
var() {}
~var() {}
var(const var &init) { init.clone(this); }
var(const var_null &init) { init.clone(this); }
const var& operator=(const var &src) { src.copy_to(this); return *this; }
const var& operator=(const var_null &src) { src.copy_to(this); return *this; }
bool is(var_null::var_type type) const { return data.obj.is_type(type); }
bool is_null() const { return data.obj.is_type(var_null::type); }
var_null::var_type get_type() const { return data.obj.get_type(); }
protected:
void clone(var *p) const { data.obj.clone(p); }
void copy_to(var *p) const { data.obj.copy_to(p); }
public:
struct var_data {
var_null obj;
int uninitialized[3]; //存放小对象的空间
};
private:
var_data data;
friend class var_null;
};

inline void var_null::copy_to(var *p) const
{
if (!p->is(get_type())) {
p->data.obj.~var_null();
clone(p);
}
else do_copy_to(p->data.obj);
}

inline void * var_null::operator new ( size_t size , var *p)
{
assert(size<=sizeof(var::var_data));
return &(p->data.obj);
}
--------------------------------------------------------------------------------

注意 var (warp) 类里面没有放 var_null 的指针, 而是放了一个 var_null 对象的实例.
而且在后面留了一小段空间. 这是这个优化方案的核心.

var 在构造的时候同时构造了一个 var_null, 但是, 当我们再赋值的时候, 如果想赋的是一个
var_null 的派生类对象, var_null 的 copy_to 会检查出来, 并且把原来这个地方的对象
析构掉(主动调用析构函数) 但是由于空间是 var 构造的时候就给出的, 所以不需要
释放内存, 然后用 clone 在原地生成一个新的对象. 这里在原地构造新对象是用重载
一个特殊版本的 new 实现的, 看 var_null 的 operator new , 它接受一个 var 指针,
然后计算出原来放 var_null 的位置, 直接返回. 这样, 原来放 var_null 对象的位置,
就放了一个新的 var_null 派生物. 由于 var_nul 的析构函数是虚的, 这个新对象的
析构函数指针位置和原来的相同, 所以 var 在析构的时候, 无论这个位置放的什么
都会正常的析构掉.

现在,由 var 管理的小对象就不需要 2 次内存分配了. 但是 var 里预留的空间有限,
对于大对象, 我们依然需要保存对象指针. 为小对象, 和大对象, 我做了两个不同的
template.

代码:--------------------------------------------------------------------------------
// 直接放值的:

template
class _var_direct_value : public var_null {
public:
enum { type=type_id };
_var_direct_value() {}
_var_direct_value(T d) : data(d) {}
operator T() { return data; }
protected:
T data;
private:
var_type get_type() const { return type; }
void do_copy_to(var_null &p) const { ((_var_direct_value &)p).data=data; }
void clone(var *p) const { new(p) _var_direct_value(data); }
};

// 现在我们可以方便的让 var_int 可以存放一个 int
typedef _var_direct_value var_int;

// 放对象指针的:

template
class _var_pointer : public var_null {
public:
enum { type=type_id };
_var_pointer() : data(new T) {}
_var_pointer(const T &init) : data(new T(init)) {}
_var_pointer(const _var_pointer &init) : data(new T(init.data)) {}
_var_pointer(const var &init) { init.clone(this); }
~_var_pointer() { delete data; }
operator T() { return *data; }
const _var_pointer& operator=(const _var_pointer &v) {
if (&v!=this) {
delete data;
data=new T(v.data);
}
return *this;
}
protected:
T *data;
private:
var_type get_type() const { return type_id; }
void do_copy_to(var_null &p) const {
_var_pointer &v=(_var_pointer &)p;
*(((_var_pointer &)p).data)=*data;
}
void clone(var *p) const { new(p) _var_pointer(*data); }
};
--------------------------------------------------------------------------------

看到这里已经累了吗? 可是还没有完 (虽然看起来问题都解决了)
我们可以实现的更完美一些 :)
如果让用户来决定什么时候该使用那个 template 实在是难为他们, 因为
需要计算 var 里的那个空间到底放不放的下想放的东西.
如果更改 var 里预留空间大小, 还会涉及到代码的改变.
所以我使用了一个 template 的技巧来完成template 的自动选择

代码:--------------------------------------------------------------------------------
template < class T, int type_id > struct __var_class {
typedef _var_direct_value _direct;
typedef _var_pointer _pointer;
template < int _Small > struct _selector {
typedef _pointer _result;
};
template<> struct _selector<1> {
typedef _direct _result;
};
template<> struct _selector<0> {
typedef _pointer _result;
};
typedef _selector< (sizeof(T)+sizeof(var_null)<=sizeof(var::var_data)) > ::_result _best;
};
// 这个template的写法来自 STL 头文件,最后一句的意思是用计算sizeof的值来判断选择哪一种 result
--------------------------------------------------------------------------------

ok. 现在使用
__var_class::_best var_string;
来定义一个 var_string 可以放置 stl 的 string

__var_class 这个 template 利用里面的 _selector 来判断 string 的 size
能不能放在 var 预留的空间里面, 把使用上面那个 template 最合适的结果类型
赋给 _best. 我们将 __var_class::_best 定义成喜欢的短名字就 ok 了

或许上面的写法还太烦琐, 定义个宏好了 :)

#define DECLARE_VAR(type_name,id) typedef __var_class::_best var_##type_name;

下面定义个 var_double 玩玩

DECLARE_VAR(double,''doub'')

var_double 就可以存放 double 了.

一般我们还希望用 var s=var_string(""hello"");
后面可以用
string ss=var.as_string(); 这样取出来.
为每种新类型加一个 as_xxx() 函数是不实际的, 可以用一个成员函数模板实现.
先在每个类型里加一个 traits

在 _var_direct_value 和 _var_pointer 模板里加上
typedef T& reference; (萃取类型用)
分别加上
T& get_value() { return data; }
T& get_value() { return *data; }
获得引用.

然后在 var 里加一个 template function
template T::reference as(T t) {
assert(is(T::type));
return ((T*)&(data.obj))->get_value();
}

大功告成

var s=var_string(""hello"");
string ss=var.as(var_string()); 这样可以取出前面 var 里放的 ""hello"" 来.
var_string() 这个参数是编译器实例化 var:as 必要的. 最后应该优化掉了.
我不知道有什么方法可以写的更漂亮一点