1. 程式人生 > >C++11:使用 auto/decltype/result_of使程式碼可讀易維護

C++11:使用 auto/decltype/result_of使程式碼可讀易維護

C++11 終於加入了自動型別推導。以前,我們不得不使用Boost的相關元件來實現,現在,我們可以使用“原生態”的自動型別推導了!

C++引入自動的型別推導,並不是在向動態語言(強型別語言又稱靜態型別語言,是指需要進行變數/物件型別宣告的語言,一般情況下需要編譯執行。例如C/C++/Java;弱型別語言又稱動態型別語言,是指不需要進行變數/物件型別宣告的語言,一般情況下不需要編譯(但也有編譯型的)。例如PHP/ASP/Ruby/Python/Perl/ABAP/SQL/JavaScript/Unix Shell等等)靠攏,通過弱化型別實現程式設計的靈活性;而是在保持型別安全的前提下提高程式碼的可讀性,易用性和通用性。要理解這點就必須對C++泛型程式設計(GP)和為什麼要泛型有一定認識,推薦閱讀:

劉未鵬:泛型程式設計:源起、實現與意義。型別自動推導帶來的最大好處就是擁有初始化表示式的負責型別宣告簡化了。很多時候,名字空間,模版成了型別的一部分,經常要寫很長的表示式,不小心寫錯了,編譯器就給爆出一堆近似與亂碼的錯誤資訊,除錯起來更是頭疼。

1) auto

簡單用法:

  1. map< int, map<int,int> > m;  
  2. // C++98/03 style:
  3. map<int, map<int,int> >::const_iterator it = m.begin();  
  4. // C++11 style
  5. const auto it = m.begin();   
其實,我們只需要知道it是迭代器就行,通過它可以訪問容器的元素。而且如果要修改m的型別,那麼導致大量的迭代器都要修改,違反了DRY(Don't Repeat Yourself,不要重複粘帖你的程式碼)原則。即使使用typedef,也不能完全避免這個問題。所以,任何人都應該使用auto!(注:auto的語義已經更改,C++98/03是修飾自動儲存期的區域性變數。但是實際上這個關鍵字幾乎沒有人用,因為一般函式內沒有宣告為static的變數都是自動儲存期的區域性變數)

本文講詳細討論auto/decltype/result_of 用法及應用場景。

auto並不是說這個變數的型別不定,或者在執行時再確定,而是說這個變數在編譯時可以由編譯器推匯出來,使用auto和decltype只是佔位符的作用,告訴編譯器這個變數的型別需要編譯器推導,藉以消除C++變數定義和使用時的型別冗餘,從而解放了程式設計師打更多無意義的字元,避免了手動推導某個變數的型別,甚至有時候需要很多額外的開銷才能繞過的型別宣告。

但是,auto不能解決精度問題:

  1. auto a = numeric_limits<unsinged int>::max();  
  2. auto b = 1;  
  3. auto c = a + b;// c is also unsigned int, and it is 0 since it has overflowed.
這並不像一些動態語言那樣,會自動擴充套件c以儲存更大的值。因此這點要注意。

auto的使用細則:

  1. int x;  
  2. int *ptr = &y;  
  3. double foo();  
  4. int &bar();  
  5. auto *a = &x; // int *
  6. auto &b = x; // int &
  7. auto c = ptr; //int *
  8. auto &d = ptr; // int *
  9. auto *e = &foo(); // compiler error, the pointer cannot point to a temporary variable.
  10. auto &f = foo(); // compiler error
  11. auto g = bar(); // int
  12. auto &h = bar(); // int &
變數a, c , d都是指標,且都指向x,實際上對於a,c,d三個變數而言,宣告其為auto *或者auto並沒有區別。但是,如果變數要是另外一個變數的引用,則必須使用auto &,注意g和h的區別。

auto和const,volatile和存在這一定的關係。C++將volatile和const成為cv-qualifier。鑑於cv限制符的特殊性,C++11標準規定auto可以和cv-qualifier一切使用,但是auto宣告的變數並不能從其初始化表示式中帶走cv-qualifier。還是通過例項理解吧:

  1. double x;  
  2. float * bar();  
  3. const auto a = foo(); // const double
  4. const auto &b = x; // const double &
  5. volatile auto *c = bar(); // volatile float *
  6. auto d = a; // double
  7. auto &e = a; // const double &
  8. auto f = c; // float *
  9. volatile auto &g = c; // volatile float * &
注意auto宣告的變數d,f都無法帶走a 和f的const和volatile。但是例外是引用和指標,宣告的變數e和g都保持了其引用物件相同的屬性。

2) decltype

decltype主要為庫作者所用,但是如果是我們需要用template,那麼使用它也能簡潔我們的程式碼。

與C完全不支援動態型別的是,C++在C++98標準中就部分支援動態型別了:RTTI(RunTime Type Identification)。RTTI就是為每個型別產生一個type_info的資料,我們可以使用typeid(var)來獲取一個變數的type_info。type_info::name就是型別的名字;type_info::hash_code()是C++11中新增的,返回該型別唯一的hash值。

在decltype產生之前,很多編譯器廠商都有自己的型別推導的擴充套件,比如GCC的typeof操作符。

言歸正傳,decltype就是不用計算表示式就可以推匯出表示式所得值的型別。

  1. template<typename T1, typename T2>  
  2. void sum(T1 &t1, T2 &t2, decltype(t1 + t2) &s){  
  3.   s = t1 + t2;  
  4. }  
  5. // another scenario
  6. template<typename T1, typename T2>  
  7. auto sum(T1 &t1, T2 &t2) ->decltype(t1 + t2)){  
  8.   return t1 + t2;  
  9. }  
很容易看出與auto的不同。例項化模版的時候,decltype也可以有用武之地:
  1. int hash(char *);  
  2. map<char *, decltype(hash(nullptr))> m;// map<char *, int> m may be more simple, but when hash value changed to other type, such as 
  3. //string, it would cause a lot of maintenance effort. 

接下來看一個更復雜的例子。首先定義Person:
  1. struct Person  
  2. {  
  3.   string name;  
  4.   int age;  
  5.   string city;  
  6. };  

我們想得到一系列的multimap,可以按照city,age進行分組。
第一個版本:
  1. template<typename T, typename Fn>  
  2. multimap<T, Person> GroupBy(const vector<Person>& vt, const Fn& keySlector)  
  3. {  
  4.   multimap<T, Person> map;  
  5.   std::for_each(vt.begin(), vt.end(), [&map](const Person& person)  
  6.   {  
  7.     map.insert(make_pair(keySlector(person), person)); //keySlector返回值就是鍵值,通過keySelector擦除了型別
  8.   });  
  9. return map;  
  10. }  

通過傳入key type,和獲取相應值的函式(可以使用lambda),就可以獲取這個multimap。但是,實際上key type就是Fn的返回值,可以不用傳入:通過keySlector(person)進行判斷。這裡就要說說如何獲取閉包的返回值型別了。獲取閉包的返回值型別的方法有三種:

  1.     通過decltype
  2.     通過declval
  3.     通過result_of


第一種方式,通過decltype:

multimap<decltype(keySlector((Person&)nulltype)), Person>或者multimap<decltype(keySlector(*((Person*)0))), Person>
這種方式可以解決問題,但不夠好,因為它有兩個magic number:nulltype和0。
通過declval:
multimap<decltype(declval(Fn)(declval(Person))), Person>
這種方式用到了declval,declval的強大之處在於它能獲取任何型別的右值引用,而不管它是不是有預設建構函式,因此我們通過declval(Fn)獲得了function的右值引用,然後再呼叫形參declval(Person)的右值引用,需要注意的是declval獲取的右值引用不能用於求值,因此我們需要用decltype來推斷出最終的返回值。這種方式比剛才那種方式要好一點,因為消除了魔法數,但是感覺稍微有點麻煩,寫的程式碼有點繁瑣,有更好的方式嗎?看第三種方式吧:

通過result_of
multimap<typename std::result_of<Fn(Person)>::type, Person>
std::result_of<Fn(Arg)>::type可以獲取function的返回值,沒有魔法數,也沒有declval繁瑣的寫法,很優雅。其實,檢視原始碼就知道result_of內部就是通過declval實現的,作法和方式二一樣,只是簡化了寫法。

最終版本:

  1. vector<Person> v = { {"aa", 20, "shanghai"}, { "bb", 25, "beijing" }, { "cc", 25, "nanjing" }, { "dd", 20, "nanjing" } };  
  2. typedeftypename vector<Persion>::value_type value_type;  
  3. template<typename Fn>  
  4. multimap<typename result_of<Fn(value_type)>::type, value_type> groupby(const vector<value_type> &v, const Fn& f)  // -> decltype(f(*((value_type*)0))),f((value_type&)nullptr)
  5. {  
  6. //typedef typename result_of<Fn(value_type)>::type ketype;
  7.   typedef  decltype(declval<Fn>()(declval <value_type>())) ketype;  
  8.   multimap<ketype, value_type> mymap;  
  9.   std::for_each(begin(v), end(v), [&mymap, &f](value_type item)  
  10.   {  
  11.     mymap.insert(make_pair(f(item), item));  
  12.   });  
  13.   return mymap;  
  14. }  

看一下最終的呼叫情況:
  1. vector<Person> v = { {"aa", 20, "shanghai"}, { "bb", 25, "beijing" }, { "cc", 25, "nanjing" }, { "dd", 20, "nanjing" } };  
  2. // group by age
  3. auto r1 = range.groupby([](const Person& person){return person.age; });  
  4. // group by name
  5. auto r2 = range.groupby([](const Person& person){return person.name; });  
  6. // group by city
  7. auto r3 = range.groupby([](const Person& person){return person.city; });  

result_of 其實就是通過decltype來推導函式的返回型別。result_of的一種可能的實現如下:
  1. template<class F, class... ArgTypes>  
  2. struct result_of<F(ArgTypes...)>  
  3. {  
  4.   typedef decltype(  
  5. 相關推薦

    C++11使用 auto/decltype/result_of使程式碼維護

    C++11 終於加入了自動型別推導。以前,我們不得不使用Boost的相關元件來實現,現在,我們可以使用“原生態”的自動型別推導了!C++引入自動的型別推導,並不是在向動態語言(強型別語言又稱靜態型別語言,是指需要進行變數/物件型別宣告的語言,一般情況下需要編譯執行。例如C/C

    《深入應用C++11程式碼優化與工程級應用》勘誤表

    轉自:http://www.cnblogs.com/qicosmos/p/4562174.html   整理一下吧,原文好亂。。。。。。。。。。。。。。。   page 4, 倒數第9,10行註釋中的變數名e和f應該改為g和h   page16,

    C++11型別推導auto

    原文:http://blog.csdn.net/huang_xw/article/details/8760403  C++11中引入的auto主要有兩種用途:自動型別推斷和返回值佔位。auto在C++98中的標識臨時變數的語義,由於使用極少且多餘,在C++11中已被刪除。前

    c++ 11 遊記 之 decltype constexpr

    script hide line variable pan sig 結果 roc .net title: c++ 11 遊記 1 keyword :c++ 11 decltype constexpr 作者:titer1 zhangyu 出處:

    閱讀《深入應用C++11代碼優化與工程級應用》

    深入 代碼優化 特性 image 技術分享 com 實例 .com 的人 雖然一直是寫C++的,但是卻對C++11了解的不是太多,於是從圖書館借了本書來看 這本書分兩大部分: 一、C++11的新特性講解 二、工程級代碼中C++11的應用 這樣的安排很合理,第一部

    c++11物件移動 & 右值引用 & 移動建構函式

    一、概述 c++ 11 新標準中最主要的特徵是可以移動而非拷貝物件的能力。很多情況下,物件拷貝後就會立即被銷燬。 在這些情況下,移動而非拷貝物件會大幅度提升效能。 在舊 C++ 標準中,沒有直接的方法移動物件。因此,即使不必要拷貝物件的情況下,我們也不得不拷貝。如果物件本身要求

    c++11 智慧指標 shared_ptr & unique_ptr

    一、背景 1. 堆記憶體、棧記憶體、靜態區記憶體 我們知道,靜態記憶體用來儲存區域性 static 物件、類 static 資料成員以及定義在函式之外的變數。而棧記憶體用來儲存定義在函式內的非 static 物件。 分配在靜態區或棧記憶體中的物件由編譯器自動建立和銷燬,對於棧

    C++11右值引用、移動語意與完美轉發

    在C++11之前我們很少聽說左值、右值這個叫法,自從C++11支援了右值引用之後,大多數人會像我一樣疑惑:啥是右值? 準確的來說: 左值:擁有可辨識的記憶體地址的識別符號便是一個左值。 右值:非左值。 左值引用:左值識別符號的一個別名,簡稱引用

    C++11function的使用

    #include <iostream> #include <functional> //1、普通函式 void func() { std::cout << __func__ << std::endl; } //2、類中的靜

    C++11bind的使用

    #include <iostream> #include <functional> //std::bind using namespace std; void func(int x, int y) { cout << x <&

    C++11自動型別推導與型別獲取

    auto 話說C語言還處於K&R時代,也有auto a = 1;的寫法。中文譯過來叫自動變數,跟c++11的不同,C語言的auto a = 1;相當與 auto int a = 1;語句。 而C++11的auto是有著嚴格的型別推匯出來的。以前是這麼寫

    C++11互斥鎖對程式效能的影響

    在多執行緒中,對資料的保護機制,我們用到了互斥量、臨界區、讀寫鎖、條件變數等方法。一直以來都有些擔心鎖會降低程式的效能,儘管它是必須的,但究竟它能降低多少呢?那只有靠資料說話,下面的程式碼是2個執行緒同時操作一個變數:class TestA { public: explic

    C++ 11delete關鍵詞和一條《Effective C++》的過時條款

    在Scott Meyers的名著《Effective C++》中的條款5(瞭解C++默默編寫並呼叫了哪些函式)和條款6(若不想使用編譯器自動生成的函式,就應該明確拒絕)中提到以下幾點: 以一個簡單的類

    c++11多執行緒

        很高興c++11的標準庫可以#include <thread>了。boost早就提供了類似功能。這時候考慮下開發商、物業公司聯合阻礙成立業主委員會的藉口之一: 會妨礙事情的正常進展,推斷也許他們也是c++的長期使用者:) 1、pthread_xx的封裝

    C++11多執行緒與鎖

    多執行緒是小型軟體開發必然的趨勢。C++11將多執行緒相關操作全部整合到標準庫中了,省去了某些坑庫的編譯,真是大大的方便了軟體開發。多執行緒這個庫簡單方便實用,下面給出簡單的例子 #include <iostream> #include <thread&

    C++11左值引用與右值引用

    C++11:左值引用與右值引用 在 C++11 的新標準中,出現了「右值引用」的說法,既然有了右值引用,那麼傳統的引用也就叫做左值引用。 右值引用 (Rvalue Referene) 是 C++ 新標準 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它實現了轉

    C++11儘量使用std::array來代替陣列

    C++11後,標準庫引入了更現代化的陣列std::array,cppreference.com是這樣介紹的: 定義於標頭檔案 <array> template< cla

    C++11強型別列舉(enum)

    // C++11之前的enum型別是繼承C的,不溫不火; // C++11對enum動刀了,加強了型別檢查,推出強型別enum型別,眼前一亮 // 使用過QT 的都知道,早就應該這麼做了,用的很爽!! // 一、C中enum型別的侷限 // 1、非強型別作用域 enum

    C++11之lock_guard學習總結和程式碼例項

    std::lock_gurad 是 C++11 中定義的模板類。定義如下: template<class _Mutex> class lock_guard { // class with destructor that un

    c++11新特性--decltype

    返回值 decltype(表示式) [返回值的型別是表示式引數的型別] 這個可也用來決定表示式的型別,就像Bjarne暗示的一樣,如果我們需要去初始化某種型別的變數,auto是最簡單的選擇,但是如果我們所需的型別不是一個變數,例如返回值這時我們可也試一下decltype。