前言:
本文展示了歐洲電腦開發商協會正在發展的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也將得到保留。