首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

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

2012-03-03 
强大的C++——模拟property模拟property属性(property)是很多时髦的面向对象编程语言提供的一种特性。通过属

强大的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也挺好的

热点排行