在C++Builder, 可以通過CB的擴充文法__property()來實現對象的Property。但這個方法必需有編譯器的支援,脫離了CB的編譯環境就無法使用。同樣的,在VC6.0以上的版本中,也實現了__declspec(property)這樣一個文法來實現property.它除了跟CB 中的perperty一樣,在跨平台方面有局限性以外,而且他不支援多態。 下面的例子可以說明:
class M
{
private:
int v;
virtual int Get()
{
return v;
}
virtual void Set(int x)
{
v = x;
}
public:
__declspec(property(get=Get,put=Set)) int XValue;
};
class MM : public M
{
virtual int Get()
{
return v+3;
}
virtual void Set(int x)
{
v = x -3;
}
};
MM m;
m.XValue = 3; //實際上調用的是M::Set(),而不是MM::Set()
int v = m.XValue; //實際上調用的是M::Get(), 而不是MM:Get();
為什麼呢?因為在編譯器進行編譯的時候,已經將這個property跟 M::Get(),M::Set()邦定在了一起。
鑒於上面所述,我們提出我們目標:
1. 要實現Property,並且不依賴於某一個開發平台,只要是當前流行的C++編譯器都可以編譯通過。
2. 必須使這種Property的實現方法符合OOP的法則。
只有達到以上兩點,才能使我們的方法有實用性。
下面我們利用C++的模板方法來實現Property. 這個方法未必是最好的,而且在實際使用的時候還會是有問題的。但也算是學習一下C++的Template了。
首先來看看Property的基本實現原理。在C++文法中,是沒有Property這樣的文法的。在C++中,Property就相當於類成員變數,我們可以把成員變數暴露在Public域,這樣我們可以任意操作他,而不用很麻煩的去調用某一個操作函數(看上去就象在VB中操作對象的Property一樣)。但實際上這是一種很不安全的方法,因為我們失去了對這個成員變數的控制,那將會發生很多意料不到的事情。 因此在C++中,Property的實現都是通過成員函數來類比實現的。比如:
class A
{
private:
int x;
public:
void get_X(int& v);
void put_X(int v);
}
類A中的成員函數X,就是通過get_X()和put_X()這兩個函數來實現訪問的。這將保證變數x盡量的處於我們的保護之中(可以在get_X,put_X中寫入我們對x的保護代碼)。
那麼能否通過某一種方法,來隱藏對get_和put_函數的調用,而類似於:
A.X_Value = 3; //其實是間接調用A.put_X(3)
int a = A.X_Value; //其實是間接調用A.get_X();
這樣的文法呢?
可以通過C++的模板技術來實現。要實現這樣的文法,我們需要一個代理模板類, 在這個模板類中,我們重載操作符"=", 在這個重載的operator =()函數中,我們調用宿主對象的put_X()函數,把值傳入. 然後我們還要重載屬性值類型的操作符, 比如屬性值的類型是 string ,則我們要重載一個叫string的操作符, 並在 operator string()中調用宿主對象的get_X()函數,把值傳出去.
下面是例子代碼
#include <stdio.h>
#include <string>
using namespace std;
template<class ClassName , typename ParamType>
class Property
{
public:
typedef void (ClassName::*Get)(ParamType& ) ; //定義一個指向實際類中進行資料操作的函數指標類型 Get,和 Set
typedef void (ClassName::*Set)(ParamType& ) ;
Property()
{
};
Property( Get pGet , Set pSet ,ClassName* pHost ):m_pfnGet(pGet),m_pfnSet(pSet),m_pHost(pHost)
{
};
operator ParamType()
{
ParamType v;
(m_pHost->*m_pfnGet)(v);
return v;
};
ParamType operator = (ParamType v)
{
(m_pHost->*m_pfnSet)(v);
return v;
};
private:
Get m_pfnGet; //儲存設定函數的指標
Set m_pfnSet; //儲存擷取函數的指標
ClassName* m_pHost; //儲存宿主對象到指標
};
class CTestClass
{
protected:
int v;
virtual void SetValue(int& x )
{
v = x;
};
virtual void GetValue(int& x )
{
x = v;
};
public:
Property<CTestClass,int> Value;
CTestClass():Value((Property<CTestClass,int>::Get)&CTestClass::GetValue,
(Property<CTestClass,int>::Set)&CTestClass::SetValue,
this
)
{
}
};
//屬性值為C++對象的例子
class CTestClass2
{
private:
string m_str;
void SetValue(string& s)
{
m_str = s;
};
void GetValue(string& s)
{
s = m_str;
}
public:
Property<CTestClass2,string> Value;
CTestClass2():Value((Property<CTestClass2,string>::Get)&CTestClass2::GetValue,
(Property<CTestClass2,string>::Set)&CTestClass2::SetValue,
this
)
{
}
};
//測試子類的函數能否正確調用
class CChild : public CTestClass
{
protected:
void SetValue(int& x)
{
v = x + 5;
}
};
int main(int argc, char* argv[])
{
CTestClass m;
m.Value = 5;
printf("m.value is %d/n",(int)m.Value);
CChild n;
n.Value = 5;
printf("n.value is %d/n",(int)n.Value);
CTestClass2 o;
o.Value = "hello,propery!";
printf("o.value is %s/n",((string)o.Value).c_str());
getchar();
return 0;
}
使用G++進行編譯.
g++ property.cpp -o property
運行 ./property
輸出:
m.value is 5
n.value is 10
o.value is hello,propery!
因為我們傳達給代理類的宿主對象的實際運行指標(this), 所以即使是宿主對象的set,get函數是虛擬函數,我們也能擷取到函數的地址,進行調用. 但是這個方法在擷取屬性值時還是有些缺陷的, 就是必須顯式的聲明調用類型. 比如上例中, printf("m.value is %d/n",(int)m.Value); 必須在m.Value 前加(int), 否則編譯器就不知道m.Value是什麼類型,就會報錯(也許有的編譯器能夠自動用Property類的opreator int 去轉換).