摘要:
本文介紹了在C++中實現“屬性 (Property)”的方法,“屬性”是我們在C#(或其它一些語言)中常常能用到的一種特性。這里介紹的實現方法使用的是標准的C++,沒有用任何其它的語言擴展。而大部分的庫或是編譯器為了實現“屬性”,往往對C++作一些擴展,就像我們在托管的C++或是C++ Builder中看到的那樣,也有的是使用普通的set和get方法,這些都不能算是真正的“屬性”。
正文:
首先,讓我們來看看什么是“屬性”。“屬性”在外觀上看起來就像類中的一個普通成員變量(或者稱為是“字段”),但它內部是通過一組set/get方法(或稱作read/write方法)來訪問類中實際的成員變量。
舉例來說,如果我有一個類A和它的一個“屬性”Count,我就可以寫出如下的代碼:
A foo;
cout << foo.Count;
Count實際上是調用了一個get函數,並返回了我們所希望得到的成員變量值。使用“屬性”而不是直接使用成員變量值的最大好處是你可控制這個“屬性”是只讀的(您只能讀出它的值而不能改變它的值)、只寫的、或是可讀可寫的。讓我們一起來實現它吧:
我們希望能實現下面的用法:
int i = foo.Count; //-- 實際上調用get函數來獲取實際的成員變量的值 --
foo.Count = i; //-- 實際上將調用set函數來設置實際的成員變量的值 --
因此,很明顯的,我們需要重載“=”運算符以便可以設置“屬性”的值,還要正確處理“屬性”的返回類型(稍后就可以看到一點)。
我們將實現一個類,名叫property,它將表現得像一個“屬性”,它的結構如下:
template<typename Container, typename ValueType, int nPropType>
class property {}
這個類模版將表現為我們需要的“屬性”。Container是一個類的類型(后面我們稱之為“容量類”),這個類就是包含要實現為“屬性”的實際成員變量、訪問這個變量的set/get方法和表現出來的“屬性”的類。ValueType是容量類內部的實際成員變量的類型(也將成為“屬性”的類型),nPropType表示“屬性”的類別:“只讀”、“只寫”或是“讀寫”。
我們還需要設置一組指針,指向容器類特定成員變量的set和get方法,同時還要重載“=”運算符,使得“屬性”可以表現得像一個變量。下面我們看看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;
}
//-- This to set a pointer to the class that contain the
// property --
void setContainer(Container* cObject)
{
m_cObject = cObject;
}
//-- Set the set member function that will change the value --
void setter(void (Container::*pSet)(ValueType value))
{
if((nPropType == WRITE_ONLY) || (nPropType == READ_WRITE))
Set = pSet;
else
Set = NULL;
}
//-- Set the get member function that will retrieve the value --
void getter(ValueType (Container::*pGet)())
{
if((nPropType == READ_ONLY) || (nPropType == READ_WRITE))
Get = pGet;
else
Get = NULL;
}
//-- Overload the '=' sign to set the value using the set
// member --
ValueType operator =(const ValueType& value)
{
assert(m_cObject != NULL);
assert(Set != NULL);
(m_cObject->*Set)(value);
return value;
}
//-- To make possible to cast the property class to the
// internal type --
operator ValueType()
{
assert(m_cObject != NULL);
assert(Get != NULL);
return (m_cObject->*Get)();
}
private:
Container* m_cObject; //-- Pointer to the module that
// contains the property --
void (Container::*Set)(ValueType value);
//-- Pointer to set member function --
ValueType (Container::*Get)();
//-- Pointer to get member function --
};
讓我們來一段段的分析程序:
下面這段代碼把Container指針指向一個有效的對象,這個對象就是我們要添加“屬性”的類(也就是容器類)的對象。
void setContainer(Container * cObject)
{
m_cObject = cObject;
}
下面這段代碼,設置指針指向容器類的set/get成員函數。這里僅有的一點限制是set函數必須是帶一個參數且返回void的函數,而get函數必須不帶參數且返回ValueType型的值。
//-- Set the set member function that will change the value --
void setter(void (Container::*pSet)(ValueType value))
{
if((nPropType == WRITE_ONLY) || (nPropType == READ_WRITE))
Set = pSet;
else
Set = NULL;
}
//-- Set the get member function that will retrieve the value --
void getter(ValueType (Container::*pGet)())
{
if((nPropType == READ_ONLY) || (nPropType == READ_WRITE))
Get = pGet;
else
Get = NULL;
}
下面這段代碼,首先是對“=”運算符進行了重載,它調用了容器類的set成員函數以實現賦值操作。然后是定義了一個轉換函數,它返回get函數的返回值,這使整個property類表現得像一個ValueType型的成員變量。
//-- Overload the '=' sign to set the value using the set member --
ValueType operator =(const ValueType& value)
{
assert(m_cObject != NULL);
assert(Set != NULL);
(m_cObject->*Set)(value);
return value;
}
//-- To make possible to cast the property class to the
// internal type --
operator ValueType()
{
assert(m_cObject != NULL);
assert(Get != NULL);
return (m_cObject->*Get)();
}
下面讓我們看看我們是如何來使用這個property類的:
就像下面的代碼中所展示的一樣:PropTest類實現了一個名為Count的“屬性”。這個“屬性”的值實際上是通過get函數從一個名為m_nCount的私有變量獲得並通過set函數把這個“屬性”值的變動寫回到m_nCount中去的。get/set函數可以任意命名,因為它們是通過它們的函數地址傳遞給property類的,就像您在PropTest類的構造函數中看到的那樣。PropTest類中的"property<PropTest,int,READ_WRITE> Count; "一行就使我們的PropTest類就擁有了一個名叫Count可以被讀寫的整型“屬性”了。您可以把Count當成是一個普通的成員變量一樣來使用,而實際上,對Count的讀寫都是通過set/get函數間接的實現的。
PropTest類的構造函數中所做的初始化工作是必須的,只有這樣才能保證定義的“屬性”可以正常的工作。
class PropTest
{
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;
};
就像下面演示那樣,您可把Count“屬性”當成是一個普通成員變量一樣來使用:
int i = 5,j;
PropTest test;
test.Count = i; //-- call the set method --
j= test.Count; //-- call the get method --
如果您希望您定義的“屬性”是只讀的,您可以這樣做:
property<PropTest,int,READ_ONLY > Count;
如果希望是只寫的,就這樣做:
property<PropTest,int,WRITE_ONLY > Count;
注意:如果您把“屬性”設成是只讀的而試圖去改寫它,將會導致一個assertion(斷言)。如果“屬性”是只寫的而您試圖去讀它,也會發生同樣的情況。
總結:
本文介紹了如何僅僅使用標准C++的特性在C++類中實現一個“屬性”。當然,直接調用set/get函數會比使用“屬性”效率更高,因為要使用 “屬性”,您就必須為類的每一個“屬性”來實例化一個property類的對象。