//轉貼自我的朋友雲風的一篇文章,
//裡面有些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 的自動選擇
代碼:--------------------------------------------------------------------------------
te
所以我使用了一個 template 的技巧來完成template 的自動選擇
代碼:--------------------------------------------------------------------------------
te