歡迎您光臨本站 註冊首頁

模擬虛構造函數的內存分配優化

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  //轉貼自我的朋友雲風的一篇文章,
//裡面有些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 必要的. 最後應該優化掉了.
我不知道有什麼方法可以寫的更漂亮一點


[火星人 ] 模擬虛構造函數的內存分配優化已經有349次圍觀

http://coctec.com/docs/program/show-post-72103.html