前言:
本文展示了欧洲计算机开发商协会正在发展的C++/CLI(一种不同的C++语言,它方便开发人员在微软的.NET框架下更容易地开发程序)语言在C++语言上的扩展。写这篇文章的目的并不是要建议标准C++包括这部分扩展,也不是对C++/CLI的认可,而只是在探讨C++/CLI语言在这一领域的发展方向。
一、基础知识
C++/CLI中的属性是类似与各种数据成员(有各种操作限制)的可操作实体,但是这种操作往往被转化为调用存取函数(这主要是"getter"和"setter"函数)。例如:
struct Demo1 {
property int Val { // 一个非常简单的整型、分级属性。
int get() const {
++Demo1::access_count;
return this-value;
}
void set(int v) {
++Demo1::access_count;
this-value = v;
}
}
private:
int value;
static unsigned long access_count;
};
int main() {
Demo1 d;
d.Val = 3; // 调用"set"操作函数。
return d.Val; //调用"get"函数。
}
存取函数的名字必须是get 或者是 set函数,两者之中的任何一个都可以被省略,但绝不能两者全省略。省略一个存取函数导致只存在一个读属性或只存在一个写属性。属性的地址是无法获取的,然而,存取函数作 为成员函数理所当然地可以被用来产生指向成员的指针常量(例如:&Demo1::Val::set)。
属性可以使用关键字"virtual"进行声明,这意味者存取操作函数是虚函数,纯虚属性函数也是可能存在的,例如:
struct VirtualProp {
virtual property int Val = 0 {
int get() const; // 纯虚函数.
virtual void set(int v); //纯虚函数,这里关键词"virtual"是多余的。
}
// ...
};
上述例子显示了通常情况下遇到的一些简单的、非静态的、分层次的属性实例。C++/CLI文档包含了大量的概念变化,下文将进行解释。
二、动机
在标准C++的上下文中,属性约定成俗地使用"get和set函数"文法,这种文法将暴露的数据和谐地转换为封闭地状态信息。在更精细的实时框架上下文中(具体的说是微软的.NET框架),属性是可以通过映射实时发觉和修改的元素。例如,现代的GUI库将它的组件参数声明为属性,可视化的界面构筑工具装载这些库,使用装载各种组件的属性列表并将结果展现到用户面前,当用户修改了一个属性,存取操作函数将被调用,例如这将触发各种GUI更新事件。
三、属性变量
除了上述代码中声明的简单的分层属性,C++/CLI还引进了其他几种类型属性变量。
(一)静态分层属性
静态分层属性使用关键字"Static"来声明,它们的存取操作函数是静态的,静态属性的存取操作与静态数据成员的存取操作非常一致。(例如:使用C::P语法来获取C类的静态属性P)
(二)不明显的分层属性
一个属性的定义(即括号内的存取操作函数声明)可以使用分号";"来代替,在这种情况下,get和set存取函数综合成一个简单的可以存取操作的属性值。例如,C++/CLI定义的一个类如下:
struct TrivialProp {
property int Val;
};
上述代码从本质上与下述代码相同:
struct TrivialProp {
property int Val {
int get() const { return this-__Val; }
void set(int v) { this-__Val = v; }
}
private:
int __Val;
};
(三)指定索引属性
使用操作数组成员的老语法,指定索引可以操作一个数值集合,下面的例子显示了一维索引属性的操作。
struct Demo2 {
property int x[std::string] {
int get(std::string s) const { ... }
void set(int v, std::string s) { ... }
}
// ...
};
int main() {
Demo2 d;
std::string s("CLI");
d.x[s] = 3; // Calls Demo2::x::set(3, s)
return d.x[s]; // Calls Demo2::x::get(s)
}
注意,指定索引的属性不能是静态变量。
多维的索引属性也是可以的,它引入的操作语法与C/C++中数组元素操作方法不太一样,例如:
struct Demo3 {
property double x[std::string, int] {
double get(std::string s, int n) const { ... }
void set(double v, std::string s, int n) { ... }
}
// ...
};
int main() {
Demo3 d;
std::string s("CLI");
d.x[s, 7] = 42.0; // Calls Demo3::x::set(42.0, s, 7)
return d.x[s, 7] != 42.0; // Calls Demo3::x::get(s, 7)
}
后面的这一个例子说明了出现在括号内的操作索引属性的逗号符号是表达式操作符号,而不是一个逗号操作符。(下面将讨论这种规则带来的后果)。
(四)默认的索引属性
除了对象被编入伪域外,默认的索引属性与指定的索引属性非常相象,对象本身可以索引(仿佛它自身有一个[]操作成员函数一样),以前的代码只要稍微改动一下就可以说明这种变化。
struct Demo4 {
property double default[std::string, int] {
double get(std::string s, int n) const { ... }
void set(double v, std::string s, int n) { ... }
}
// ...
};
int main() {
Demo4 d;
std::string s("CLI");
d[s, 7] = 42.0; // Calls Demo4::default::set(42.0, s, 7)
return d[s, 7] != 42.0; // Calls Demo4::default::get(s, 7)
}
请关注关键词"default"代替属性名的用法。
四、一些技术性问题
欧洲计算机制造商协会(C++/CLI标准的制订者)已经研究并解决了引入属性所带来的若干问题,下面这些内容尤其值得关注。
(一)多维索引属性的操作
p-x[2, 3]表达式拥有不同的意思,这要视成员x是否是属性(这种情况下逗号分隔两个索引属性)或其它成员变量(这种情况下逗号是个操作符号,表达式的意思等同于p-x[3])而定。为了在一个属性索引中获取逗号操作符的效果,开发人员可以使用圆括号(即p-x[(2, 3)])。
(注意,在依赖模版的表达式中,这将产生模糊性,并且直到实例化时问题才能得到解决)
(二)属性名与类型名冲突
微软.NET框架带有很多包含属性的类(这些类最初并不是使用C++/CLI来开发的),这些包含的属性名与属性类型的名字相同,例如:
typedef int Color;
struct Conflict {
property Color Color { // Property name hides type name
typename Color get() const;
void set(typename Color);
}
// ...
};
}
为了帮助在这种上下文中书写代码,C++/CLI计划添加语法,使用关键词typename来标识不标准的类型(特别是"属性"),查找标志符的过程中将被忽视。上述的代码就以这种新的形式使用typename关键词。
(三)重载的索引属性
索引属性可以被重载,即,几个指定索引属性可以使用同一个名字共存于同一个类中,假定它们可以根据属性的类型来区分开来。相似地,默认的索引属性可以使用其他属性或操作符[]来重载。解决两意性与重载行为的规则已经被建立起来,来处理上述情况。
(四)保留的成员名字
C++/CLI属性通过综合特定的成员来实现,这些成员的名义由微软的.NET框架来规定,并且必须得到保留。
如果一个类包含分层的属性或指定索引属性X,成员名 get_X 和set_X在类中得到保留(即使属性仅仅包含一个操作函数也是这样)。相似地,如果一个类包含有一个默认的索引属性,类中的成员函数get_Item 和set_Item也将得到保留。