開發測試環境:Visual C++ 7.0, Windows XP sp1, Windows 2000 sp3
摘要
本文試著在C++中不使用任何擴展技術模擬C#(或其他語言)中的屬性特征。大多數在C++實現屬性的庫和編譯器使用擴展技術,如Managed C++或C++ Builder,或者他們使用如通常函數的set和get方法,但那不是屬性。
詳述
我們首先看一下什麼是屬性。一個屬性表現為一個字段或者成員變量,但它通過read和write方法或者get和set方法暗中操作變量。
例如,若存在類A和它的屬性Count,我可以寫如下的代碼:
A foo;
實際上Count調用它的get函數返回當前的變量值。你可以將屬性定為只讀(你可以讀取它但不能修改它)、只寫或者可讀寫,這就是使用屬性而不直接使用變量的的一個最大好處了。好了,讓我們開始來實現它:
Cout << foo.Count;
我們需要能做如下的事:
int i = foo.Count; //--調用get函數得到值
因此,很明顯的我們需要重載''=''操作符使其能設定變量的值,同時也要重載該屬性的返回值(在下面我們將會看到的)。
foo.Count = i; //-- 調用set函數設定值
我們將實現一個稱為property的類,它做的就像一個屬性,聲明如下:
template <typename Container, typename ValueType, int nPropType>
這個模板類表示的是我們的屬性。Container是我們要在其中包含屬性的類變量,set和get方法以及屬性的類的類型。ValueType是內部變量即要定義的屬性的類型,nPropType定義屬性的讀寫標志:只讀、只寫或可讀寫。現在我們需要一個指向從包含屬性的類Container到屬性類property的set和get方法的指針,同時重載''=''操作符以使得屬性能象變量起那樣作用。現在我們來看property類的全部定義
class property {}
#define READ_ONLY 1
#define WRITE_ONLY 2
#define READ_WRITE 3
template <typename Container, typename ValueType, int nPropType>
class property
{
public:
property()
{
m_cObject = NULL;
Set = NULL;
Get = NULL;
}
//-- 將m_cObject指向包含屬性的container類 --
void setContainer(Container* cObject)
{
m_cObject = cObject;
}
//-- 設定可改變屬性值的set成員函數 --
void setter(void (Container::*pSet)(ValueType value))
{
if((nPropType == WRITE_ONLY) || (nPropType == READ_WRITE))
Set = pSet;
else
Set = NULL;
}
//-- 設定可檢索屬性值的get成員函數 --
void getter(ValueType (Container::*pGet)())
{
if((nPropType == READ_ONLY) || (nPropType == READ_WRITE))
Get = pGet;
else
Get = NULL;
}
//-- 重載''=''號操作符使其能用set成員設定屬性值--
ValueType operator =(const ValueType& value)
{
assert(m_cObject != NULL);
assert(Set != NULL);
(m_cObject->*Set)(value);
return value;
}
//-- 使屬性類能轉換為內部類型成為可能--
operator ValueType()
{
assert(m_cObject != NULL);
assert(Get != NULL);
return (m_cObject->*Get)();
}
private:
Container* m_cObject; //-- 指向包含屬性的類模塊 --
void (Container::*Set)(ValueType value);
//-- 指向set成員函數的函數指針 --
ValueType (Container::*Get)();
//-- 指向get成員函數的函數指針 --
};
現在讓我們來一段一段地看這些代碼:
在下面的代碼中,僅僅將Container指針指向一個有效的包含屬性的實例。
void setContainer(Container* cObject)
{
m_cObject = cObject;
}
下面的代碼,設定指針指向包含屬性的類中的set和get成員函數,其set和get成員函數度有,唯一的限制即set成員函數必須有一個ValueType型的參數並無返回值,get成員函數沒有參數,但要返回ValueType型值。
//-- 設定可改變屬性值的set成員函數 --
在如下的代碼中,第一部分是''=''操作符的重載,它調用包含屬性的類中的set函數設定其屬性的值。第二部分則為了使整個屬性類象ValueType類型一樣起作用,所以它返回包含屬性的類中get函數的返回值。
void setter(void (Container::*pSet)(ValueType value))
{
if((nPropType == WRITE_ONLY) || (nPropType == READ_WRITE))
Set = pSet;
else
Set = NULL;
}
//-- 設定可檢索屬性值的get成員函數 --
void getter(ValueType (Container::*pGet)())
{
if((nPropType == READ_ONLY) || (nPropType == READ_WRITE))
Get = pGet;
else
Get = NULL;
}//-- 重載''=''號操作符使其能用set成員設定屬性值--
ValueType operator =(const ValueType& value)
{
assert(m_cObject != NULL);
assert(Set != NULL);
(m_cObject->*Set)(value);
return value;
}
//-- 使屬性類能轉換為內部類型成為可能--
operator ValueType()
{
assert(m_cObject != NULL);
assert(Get != NULL);
return (m_cObject->*Get)();
}
現在我們來看看怎樣使用它:
如下所示,在PropTest類中定義了一個叫做Count的簡單屬性。Count的實際值將保存到或檢索之在PropTest的私有成員變量"m_nCount"中,通過PropTest的get和set方法。get和set方法可以使用任何的變量名字,只需他們的地址能被傳遞到property類中,如下面的PropTest構造函數裡面的代碼般,代碼行" property<PropTest, int, READ_WRITE> Count; "讓我們在PropTest中得到可讀寫的int型的Count屬性。現在你可以使用如一般的成員變量般使用使用Count屬性了,但實際上你是間接地調用它set和get方法。
要使Count屬性能成功工作,必須先在PropTest的構造函數裡面對其進行初始化。
class PropTest
如下所示,你可以象使用普通變量一樣使用Count屬性。
{
public:
PropTest()
{
Count.setContainer(this);
Count.setter(&PropTest::setCount);
Count.getter(&PropTest::getCount);
}
int getCount()
{
return m_nCount;
}
void setCount(int nCount)
{
m_nCount = nCount;
}
property<PropTest,int,READ_WRITE> Count;
private:
int m_nCount;
};int i = 5,j;
要使用只讀的屬性,你可以創建如下的property實例:
PropTest test;
test.Count = i; //-- 調用set函數 --
j= test.Count; //-- 調用get函數 --property<PropTest,int,READ_ONLY > Count;
要使用只寫的屬性,你可以創建如下的property實例:property<PropTest,int,WRITE_ONLY > Count;
注意:如果你將某一屬性設為只讀,當你對其賦值時,將引發assertion診斷。同理,當讀取只寫的屬性時
也同樣會引發assertion診斷。
小結
本文展示了在C++只用標准的C++特性而不使用其他任何的擴展技術來實現屬性。當然,直接使用set和get函數效率
要更高些,因為本文中的方法需要為每一個屬性定義一個property類實例。
本文配套源碼