在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 去转换).