1、概述
本人對“C++實現C#屬性概念”的研究決定並非一時衝動,而是原自於對技術的熱愛。
用過C#的C渣渣程式設計師/媛都非常喜歡C#裡面的屬性的概念(注意,C#裡的屬性和成員變數之間是有區別的),可是C渣渣裡並沒有屬性這個概念,於是,很多程式設計師/媛都想辦法使用C渣渣抽象出類似C#中的屬性的概念,當然我也不例外。
看過很多網友and同事寫的C++屬性實現方案,But似乎都沒有找到很符合口味的實現,不是太難看懂就是太難使用。
so,本文介紹的是博主在工作過程中封裝的C++實現屬性的方法,不敢說多好用,但從使用形式上看,個人覺得與我瞭解到的方案中,博主實現的方案有如下特點:
1、易用且非常接近C#的使用風格。
2、程式碼量少,很容易看懂。
下面將一步一步介紹一些實現上的細節。
2、C# 屬性的概念
本想略過對C#屬性的介紹,但考慮到讀者可能沒使用過C#,也不知道C#的屬性到底有什麼用,因此還是稍微從使用的角度先解釋一下C#中屬性的概念(如果您對屬性已經足夠了解,可直接跳過本章或直接從下面第2點的“C++實現效果與C#效果對比”開始)。
一下內容包含如下部分:
1、簡單示例程式碼介紹C#中的屬性;
2、C++實現效果與C#效果對比;
1、簡單示例程式碼介紹 C# 中的屬性
我們來看第一段C#程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html class TestDemo { public String Name { get; set; } } class Program { static void Main(string[] args) { TestDemo test = new TestDemo(); test.Name = "test"; System.Console.WriteLine(test.Name); } } |
上面程式碼中,我們建立了一個String型別的屬性Name,這個時候我們,我們的屬性其實跟一個成員變數沒啥區別,只是一個成員變數罷了。
另外,還需要讀者瞭解的是,屬性裡的get和set分別控制屬性的讀寫許可權:
1、如果只有get,那麼Name為只讀模式;
2、如果只有set,那麼Name為只寫模式;
只讀模式示例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html class TestDemo { public String Name { get; } } class Program { static void Main(string[] args) { TestDemo test = new TestDemo(); test.Name = "test";//只讀屬性,賦值不允許,編譯出錯 System.Console.WriteLine(test.Name); } } |
只寫模式示例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html class TestDemo { public String Name { set; } } class Program { static void Main(string[] args) { TestDemo test = new TestDemo(); test.Name = "test"; System.Console.WriteLine(test.Name); //只寫模式,test.Name取值不允許,編譯失敗 } } |
我們再來看一個複雜點的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html class TestDemo { public String Name { get { return _name; } set { _name = value; TestWork(); } } public void TestWork() { System.Console.WriteLine(_name); } private String _name; } class Program { static void Main(string[] args) { TestDemo test = new TestDemo(); test.Name = "test"; } } |
以上程式碼中,我們執行到第28行的時候,會進入到第10行,最終執行結果列印出“test”。
是的,其實很簡單,如果沒有屬性,我們完全可以用一個私有的成員函式,然後為每個成員函式新增一個get和set的方法(java下的通常做法),如下程式碼中的TestDemo完全能實現上面程式碼中屬性的做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html class TestDemo { public String GetName() { return _name; } public void SetName(String value) { _name = value; TestWork(); } public void TestWork() { System.Console.WriteLine(_name); } private String _name; } …… |
OK,那為什麼要有屬性呢?
其實追求的真是一種書寫方法吧,屬效能讓我們像訪問成員變數一樣訪問直接賦值和取值,同時,在賦值和取值的時候,能根據需求呼叫相應的內部實現函式。
2、C++實現效果與C#效果對比
請讀者對比下左右兩邊的屬性Name的定義方式,C#裡面的get和set方法在C++11實現中,使用了property_getter和property_setter實現,property_getter和property_setter中程式碼的實現方式與C#均一致。
OK,看完屬性效果比較,如果您覺得對你的胃口,那麼可以繼續往下看,下面會有關於proerty_rw更詳細的說明。
3、如何使用 C++ 11 實現 C# 屬性的概念模式
本章我們將一步步介紹C++11實現屬性三種形式分別是:
1、property_rw :對應C#的讀寫模式(get和set均有)
2、property_r :對應C#的只讀模式(沒有set)
3、property_w :對應C#的只寫模式(沒有get)
1、property_rw 介紹
property_rw的實現程式碼很簡單,但需要大家對C++11中的std::function和lamda表示式有所瞭解,如果您不是很瞭解或在下面介紹中覺得難懂,可以先看看我之前寫的關於C++11的總結文章:【乾貨】C++11常用特性的使用經驗總結,對您理解本章內容會有幫助。
proerty_rw原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html #include<functional> #define property_setter(variableType) [&](variableType value) #define property_getter(variableType) [&]()->variableType template <typename ValueType> class property_rw { public: typedef std::function<void(ValueType value)> Setter; typedef std::function<ValueType(void)> Getter; explicit property_rw(Setter setter, Getter getter) : m_setter(setter), m_getter(getter) {} property_rw& operator=(ValueType value) { this->value = value; m_setter(this->value); return *this; } property_rw& operator=(const property_rw & instance) { this->value = instance.m_getter(); m_setter(this->value); return *this; } operator ValueType() { return m_getter(); } private: ValueType value; Setter m_setter; Getter m_getter; }; |
上面程式碼我們可以看出,property_rw是一個模板類,ValueType為屬性的型別名,因此大家可以想到,我們的屬性其實是一個類物件。
因此,我們來看一個最簡單的使用示例:
1 2 3 4 5 6 7 8 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html #includestring> #include"property.h" class TestDemo { public: property_rwstring> Name; }; |
需要注意:#include需要放在#include”property.h”之前,同理,其他非基本資料型別引用標頭檔案均需如此,這主要是由於proerty.h內部巨集定義的property_setter和property_getter所導致,要保持屬性的書寫風格,目前暫時沒有很好的解決這個問題。
上面的程式碼無法編譯通過,但我們先看看形式,我們定義了一個string型別的屬性Name。
從proerty_rw的建構函式看explicit property_rw(Setter setter, Getter getter),我們定義這個屬性,需要給這個屬性賦值兩個引數,分別為Setter和Getter。Getter和Setter型別分別為std::function定義的兩個可執行物件型別。在【乾貨】C++11常用特性的使用經驗總結文章中,我們介紹了std::function定義的可執行物件可以有三種形式的賦值,分別是:
1、同形式(返回值和引數相同)函式指標;
2、同形式的類成員函式;
3、同形式的lamda表示式函式;
為了統一外面的使用形式,我們使用巨集的方式定義(property_rw原始碼第3、4行)property_getter和proerty_setter,該巨集定義其實是限制外部使用lamda表示式方式(當然,這沒能從編譯源頭限制,其實外部還是可以使用第1、2兩種方式)。我們來看下property_getter和proerty_setter的定義:
#define property_setter(variableType) [&](variableType value) //定義lamda表示式的頭部,[&]表示對定義範圍內的變數取值許可權為引用形式,引數為variableType
#define property_getter(variableType) [&]()->variableType //property_getter的lamda表示式返回值為variableType
明白了property_getter和proerty_setter的定義,我們來看下Name的初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html class TestDemo { public: property_rwstring> Name = property_rwstring>( property_setter(std::string) { }, property_getter(std::string) { return "test"; } ); }; |
這個時候我們來看Name的初始化就非常清晰了,其實就是給property_rw建構函式傳遞了兩個lamda表示式定義的函式,經過我們的巨集封裝,使我們的屬性使用風格看起來與C#形式很像。
我們再來看看property_rw 中的函式,控制讀取屬性的函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html property_rw& operator=(ValueType value) { this->value = value; m_setter(this->value); return *this; } property_rw& operator=(const property_rw & instance) { this->value = instance.m_getter(); m_setter(this->value); return *this; } |
加入我們現在有兩個屬性string型別的屬性,名字分別為Name1,Name2,那麼什麼時候會呼叫上面兩個函式呢?
1 2 3 |
TestDemo test; test.Name1 = "test"; //呼叫第一個operator=()函式 test.Name2 = test.Name1; //呼叫第二個operator=()函式 |
property_rw中控制寫熟悉的函式:
1 2 3 4 |
operator ValueType() { return m_getter(); } |
示例:
1 2 3 |
TestDemo test; test.Name = "test"; std::string str = test.Name //呼叫寫屬性控制函式 |
小結:
property_rw的模板類實現簡單吧,沒有太多的邏輯程式碼,都是普通的類設計而已,關鍵需要大家有一些編碼技巧。
2、property_r、property_w 介紹
property_r和property_w就更加簡單了,我麼只需要把property_rw中的控制讀或控制寫的屬性函式去掉,就可以變成只讀或只寫的屬性型別。
property_r 原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html template class property_r { public: typedef std::functionvoid(ValueType value)> Setter; typedef std::functionvoid)> Getter; explicit property_r(Getter getter) : m_getter(getter) {} operator ValueType() { return m_getter(); } private: ValueType value; Getter m_getter; }; |
property_w原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html template class property_w { public: typedef std::functionvoid(ValueType value)> Setter; explicit property_w(Setter setter) : m_setter(setter) {} property_w& operator=(ValueType value) { this->value = value; m_setter(this->value); return *this; } property_w& operator=(const property_w & instance) { this->value = instance.m_getter(); m_setter(this->value); return *this; } private: ValueType value; Setter m_setter; }; |
property_r和property_w就沒有什麼可以介紹的了,基本和property_rw類似。
3、完整屬性測試程式碼介紹
下面是一個比較完整的測試例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
//示例程式碼1.0 http://www.cnblogs.com/feng-sc/p/5742689.html #include #includestring> #include"property.h" class TestDemo { public: property_rwstring> Name = property_rwstring>( property_setter(std::string) { m_rw_name = value; }, property_getter(std::string) { return m_rw_name; } ); property_rstring> ReadOnlyName = property_rstring>( property_getter(std::string) { return m_readonly_name; } ); property_wstring> WriteOnlyName = property_wstring>( property_setter(std::string) { m_writeonly_name = value; TestWork(); } ); void TestWork() { std::cout "TestWork()::m_writeonly_name--" std::endl; } private: std::string m_rw_name; std::string m_readonly_name = "I m read only name"; std::string m_writeonly_name = ""; }; int main() { TestDemo test; test.Name = "This is test name!"; std::string str = test.Name; std::string readonly = test.ReadOnlyName; std::cout "Test read and write,Name:" std::endl; std::cout "Test readonly, msg:" readonly std::endl; test.WriteOnlyName = "This is write only property!"; } |
執行結果:
4、總結
本文我們首先介紹了C#屬性概念,以及為什麼使用屬性,再一步一步引出我們如何使用C++11實現C#屬性的效果。整篇文章所述沒有太多邏輯的問題,單純用我們所熟知的C++11知識加上我們的一些編碼技巧,最後我們用很普通的做法實現了我們想要的效果。程式設計有時候給人的感覺就是這樣,先要有一個好的概念(如本文中的屬性概念),然後想辦法用我們熟知的知識需完美地把它實現,這會讓你蠻有成就感!