注冊博客有一段時間,一直很忙沒有寫技術文章,今天把近期心得寫出來一起分享。
c++沒有反射機制,我們可以通過c++的語言特性去實現同樣的功能。本文介紹使用type_info實現的一些發射特性。
1 type_info簡介:
class type_info {
public:
_CRTIMP virtual ~type_info();
_CRTIMP int operator==(const type_info& rhs) const;
_CRTIMP int operator!=(const type_info& rhs) const;
_CRTIMP int before(const type_info& rhs) const;
_CRTIMP const char* name() const;
_CRTIMP const char* raw_name() const;
private:
void *_m_data;
char _m_d_name[1];
type_info(const type_info& rhs);
type_info& operator=(const type_info& rhs);
};
(注: c++0x01增加了hash_code方法,獲取類名對應的hash值)
使用type_info需要注意3點:
(1)type_info的構造函數和賦值操作符都為私有。因此不要試圖去定義或復制一個type_info對象。創建type_info對象的唯一方法是使用typeid操作符。如果想使用type_info的引用,可以使用const type_info& info = typeid(foo)。
(2) type_info::name返回的是類似於"class CBase*"之類的名字, 而且c++標准只是告訴編譯器需要實現type_info::name函數,不同編譯器編譯運行后輸出不一樣。
(3) 對於父類指針類型變量,typeid(ptr).name不具有多態性, 仍返回此父類的指針類名,如"class CBase*"。如果直接傳入對象如typeid(*ptr).name則具有多態性,將返回"class CDevievd"類似的子類類名。
2 巧用type_info
(1) 得到類名的一種方法是在類中實現class_name()方法,如
class CDataType
{
public:
virtual char* class_name() { return "CDataType"; }
};
這種方法的一個缺點就是強迫每一個類必須實現這個函數,否則后續的工作就無法完成。
下面是一個為數據注冊創建者,並根據數據類型獲取創建者的例子,看一下它是如何工作的。
// 數據
class CDataType
{
public:
virtual char* class_name() { return "CDataType"; }
};
class CDataTypeA : public CDataType
{
public:
virtual char* class_name() { return "CDataTypeA"; }
};
class CDataTypeB : public CDataType
{
public:
virtual char* class_name() { return "CDataTypeB"; }
}
// 數據創建者
class CreatorType {};
class CreatorForDataA : public CreatorType {};
class CreatorForDataB : public CreatorType {};
typedef std::string KeyType;
static std::map<std::string, CreatorType*> nameMap;
void RegistCreator(KeyType key, CreatorType* creator)
{
nameMap[key] = creator;
}
CreatorType* GetCreator( CDataType* data )
{
return nameMap[data->class_name()];
}
void main()
{
RegistCreator( "CDataTypeA", new CreatorForDataA );
RegistCreator( "CDataTypeB", new CreatorForDataB );
CDataTypeA* dataA = new CDataTypeA;
CreatorType* creator = GetCreator(dataA);
}
為每個數據強制加一個class_name虛函數看起來是否有點冗余?
下面我們用type_info來實現相同的功能。
// 數據
class CDataType {};
class CDataTypeA : public CDataType {};
class CDataTypeB : public CDataType {};
CreatorType* GetCreator( CDataType* data )
{
return nameMap[typeid(*data).name()];
}
void main()
{
RegistCreator( "CDataTypeA", new CreatorForDataA );
RegistCreator( "CDataTypeB", new CreatorForDataB );
CDataTypeA* dataA = new CDataTypeA;
CreatorType* creator = GetCreator(dataA);
}
比較一下,這個版本的代碼要簡單許多了,也有較好的擴展性,或許我們可以放松一下了。
可是沒過多少天問題就來了,有的同事擔心大量的字符串比較會影響速度,雖然map是很高效的。擔心確實是不必要的,殺手鐧還在你的手中——hash_code。
hash_code是把字符串映射到一個唯一整數,使用整數作為map鍵值效率要比string高許多。廢話不說,上代碼。
typedef int KeyType;
static std::map<KeyType, CreatorType*> nameMap;
void RegistCreator(KeyType key, CreatorType* creator)
{
nameMap[key] = creator;
}
CreatorType* GetCreator( CDataType* data )
{
return nameMap[typeid(*data).hash_code()];
}
void main()
{
RegistCreator( typeid(CDataTypeA).hash_code(), new CreatorForDataA );
RegistCreator( typeid(CDataTypeB).hash_code(), new CreatorForDataB );
CDataTypeA* dataA = new CDataTypeA;
CreatorType* creator = GetCreator(dataA);
}
如果你的c++版本不夠,發現type_info根本沒有hash_code方法,沒有關系,可以使用下面的方法代替。
size_t hash_code( const type_info& info )
{
// hash name() to size_t value by pseudorandomizing transform
const char *_Keyval = info.name();
size_t _Val = 2166136261U;
size_t _First = 0;
size_t _Last = _CSTD strlen(_Keyval);
size_t _Stride = 1 + _Last / 10;
for(; _First < _Last; _First += _Stride)
_Val = 16777619U * _Val ^ (size_t)_Keyval[_First];
return (_Val);
}
本文只是拋磚引玉,希望各位朋友能有更多的發現。
2012-03-16
<轉載請注明作者和出處>