读书人

强大的C++模拟property解决办法

发布时间: 2012-03-03 15:33:02 作者: rapoo

强大的C++——模拟property
模拟property

属性(property)是很多时髦的面向对象编程语言提供的一种特性。通过属性,程序员可以如同访问成员数据一样访问一组get/set函数:
obj.x=200; int y=obj.y;
开发者可以通过定制get和set函数实现只读/只写/读写访问。更进一步可以提供复杂的数据运算。比如,float sum+=product1.money,其中money属性可能并没有对应哪个成员数据,而是由单价*数量获得的。
C++没有属性。可能是标准委员会认为属性并没有决定性的意义。而同时,属性的功能也可以由public数据成员,或者get/set成员函数对实现。因而,无须再多一个语言特性,来增加编译器开发人员的烦恼。另外,C++可以在一定程度上模拟属性。尽管不能完全达到语言内置属性的功能,但也足以打消C++对属性的渴求。在标准委员会看来,C++还有更重要的特性需要探讨。
这里,我将逐步给出几套在C++中实现属性的方案。这些方案在使用上比较接近内置的属性,但各自又有不同的侧重点。
我知道。C#、C++/CLI或Java的程序员会说:用C#、C++/CLI或Java吧,这里有真正的属性,何必死抱着C++不放呢;而C++程序员也会说:get/set成员函数对工作得很好,根本不需要什么“模拟”的属性。
我承认,模拟出来的属性的确比不上内置的属性,也有些画蛇添足。但是我在这里模拟C++的属性,并非仅仅为了让C++具备这个特征。而是试图通过模拟属性这个任务,来充分展示C++强大的功能,并且演示如何利用C++的一些高级特性(OOP、GP等)解决一个颇为棘手的问题。
好,我们从头开始。我们先考察一下内置属性的特点(以C++/CLI为例):
//定义属性:
ref class X
{
property int a;
property float b {
float get() {

}
void set(float v) {

}
}
property String^ c[int] {
String^ get(int i) {

}
void set(int i, String^ v) {

}
}
};

//访问属性:
Xx;
x.a=21;
float f=x.b;
x.c[3]=”ok”;

属性的使用非常简单。我们的模拟属性应该在形式上尽可能接近。
关于属性,Bjane Stroustrup曾经给出了一个模拟实现的大概方案(出处和具体内容我记不清了,正在努力查找):
class X;

class PropA
{
friendclass X;
public:
operator int () {
return_val;
}
void operator=(int v) {
_val=v;
}
private:
int_val;
};

class X
{

public:
PropAa;

};
这里使用了一个包装类,封装了属性的数据。通过重载转换操作符operator int()和赋值操作符operator=()实现数据的存取控制。这里使用的所有方法便是以此为基础的。
这里不打算一下子给出一个完整的方案,而是先实现最简单的模拟属性,然后逐步提高要求,逐步实现一个完整的模拟属性方案。这里,我们做一些定义,便于后面讲解:简单属性,属性仅对应一个对象,但拥有访问控制的属性(读写控制);复杂属性,属性的get/set操作对应一组复杂的运算或操作,也拥有访问控制的属性。
首先来看最简单的情况:属性仅仅对应一个对象,并且是读写的。下面是实现的代码:
template <typename T>
class prop
{
public:
operator T() {
return_val;
}
T& operator=(const T& val) {
_val=val;
return_val;
}

private:
T_val;
};
这是一个类模板,定义了一个属性。其核心便是operator T()和operator=()。通过这两个操作符,我们可以实现如下的代码:
class Test
{
public:
prop <int> a;

};

Testt;
t.a=213;
int x=t.a;
现在,我们可以为这个简单的属性加上访问控制了。访问控制包括只读、只写和读写三种形式。这三种形式通过转型操作符和赋值操作符的不同组合实现:只读拥有转型操作符而没有赋值操作符;只写拥有赋值操作符而没有转型操作符;读写两者都有。为了实现同一个模板的三种形态,我们需要用到局部特化这项功能:
//枚举:访问控制类型,包括只读、只写和读写
enum PropAccType
{
read=1,
write=2,
read_write=3
};

template <typename T, class Host, int AccCtrl=read_write>
class prop
{
public:
friend Host;
operator T() {
return_val;
}
T& operator=(const T& val) {
_val=val;
return_val;
}

private:
T_val;
};

template <typename T, class Host>
class prop <T, Host, read>
{
public:
friend Host;
operator T() {
return_val;
}

private:
prop <T, Host, read> & operator=(const prop <T, Host, read> & val) {
return*this;//赋值拷贝操作符必须定义,并且是private的。


}// 因为,若不定义成private,编译器会自
// 动生成一个。这可不是我们想要的。
// operator T()不存在这个问题。
T_val;
};

template <typename T, class Host>
class prop <T, Host, write>
{
public:
friend Host;
T& operator=(const T& val) {
_val=val;
return_val;
}

private:
T_val;
};
相比最原始的prop模板,这组prop模板增加了两个模板参数。AccCtrl用于访问控制。Host则是prop生成的最终对象的外围类(包容prop <T> 的对象的类)。Host存在的目的是为了允许prop <> 通过friend Host;语句,赋予外围类访问其私有对象的能力。这样,外围类编可以不受属性的访问控制,直接读写属性的数据成员。
prop模板有三个定义,一个基础模板定义,和两个特化的版本。基础模板的定义对应读写型的属性,并且为AccCtrl指定了默认值。这样,读写型的属性可以用最简单的形式定义:
prop <int, X> a;
两个特化的版本分别对应只读和只写型的属性,使用时需要指定属性的访问控制方式:
prop <int, X, read> b;
prop <int, X, write> c;
对于拥有如此属性的类,可以象内置属性一样访问:
class X
{
public:
prop <int, X> a;
prop <float, X, read> b;
prop <string, X, write> c;

public:
void fun() {//由于X是prop <> 的friend class,所以
float r=a._val+b._val;// X的成员函数可以直接访问a,b,c的private
c._val=format(“{0}”)%r;// 成员。避免访问控制的限制。
}
};

X x;
x.a=23;
int a=x.a;
x.b=34.5;//编译错误,无法赋值
float b=x.b;
x.c=”property c”;
string c=x.c; //编译错误,无法读取
由于模板特化的作用,当我们使用read实例化prop <> 时,编译器便会选择相应的特化版本,予以实例化。而相应的特化版本只定义了operator T()操作符。所以,当我们试图对这个属性赋值时,便会引发编译错误,表明违反了访问规则。write的情况也是一样。若使用read_write或默认值实例化prop <> 时,编译器选择了基本模板,该版本重载了两个操作符,使得读写访问都可以实现。
下面,让我们从另一个角度实现同样功能的属性。前面的方法实现的属性数据对象是保存在属性内的。为此,prop <> 需要通过friend Host赋予外围类访问其private成员的权利。因此,prop <> 的模板参数多了一个Host。如果我们将属性的数据对象保存在外围类中,那么可以省去friend的操作,可以简化prop <> 的结构:
template <typename T, int AccCtrl=read_write>
class prop
{
public:
prop(T& v) : _val(v){}
operator T() {
return_val;
}
T& operator=(const T& val) {
_val=val;
return_val;
}

private:
T&_val;
};

template <typename T >
class prop <T, read>
{
public:
prop(T& v) : _val(v){}
operator T() {
return_val;
}

private:
T& operator=(const T& val) {
_val=val;
return_val;
}
T&_val;
};

template <typename T>
class prop <T, write>
{
public:
prop(T& v) : _val(v){}
T& operator=(const T& val) {
_val=val;
return_val;
}

private:
T&_val;
};
prop <> 中保存的不再是T的对象,而是T的对象的引用。同时,还增加了一个构造函数,用以初始化这个引用。此时,外围类需要这样使用属性:
class X
{
public:
X() : a(_a), b(_b), c(_c) {} //用外围类的成员数据初始化属性

prop <int> a;
prop <float, read> b;
prop <string, write> c;

private:
int_a;
float_b;
string_c;
};
尽管属性的定义省去了外围类X作为类型实参,但却增加了属性初始化的要求。这两种方法实质是一样的,使用上各有利弊,采用什么方法,完全看程序员的喜好了。


[解决办法]
嗯,第一个支持先

------解决方案--------------------


先顶一下
[解决办法]
有点意思
[解决办法]
mark 学习

[解决办法]
LZ,牛人!对LZ的仰慕如滔滔江水,连绵不绝;又如黄河泛滥,一发而不可收拾~~
UP
[解决办法]
mark
[解决办法]
up
[解决办法]
学习了
[解决办法]
mark,学习
[解决办法]
很强大 = =
[解决办法]
C++没有属性,可能是标准委员会认为属性并没有决定性的意义。

-- 这帮委员们个个都居心不良
[解决办法]
Mark~
[解决办法]
哈...其实要不要属性... C#阵营自己也不统一的....

编写 ".Net框架设计 "的作者, .Net的泰山北斗级人物,建议大家不要用属性..

呵呵...

个人觉得无所谓... set/get也挺好的

读书人网 >C++

热点推荐