本文翻譯自modern effective C++,由於水平有限,故無法保證翻譯完全正確,歡迎指出錯誤。謝謝!
博客已經遷移到這里啦
我確信我們都同意使用STL容器是一個好主意,並且我希望在Item 18中能讓你相信使用std::unique_ptr也是一個好主意,但是我猜想,我們中沒有任何一個人想多次寫這樣的類型:“std::unique_ptr<std::unordered_map<std::string, std::string>>”。光是想想就感覺,得“腕管綜合症”的風險會增加。
為了避免這樣的醫學上的悲劇很簡單,引進typedef:
typedef
std::unique_ptr<std::unordered_map<std::string, std::string>>
UPtrMapSS;
但是typedef太太太C++98了。當然,它們能在C++11中工作,但是C++11也提供了別名聲明(alias declaration):
using UPtrMapSS =
std::unique_ptr<std::unordered_map<std::string, std::string>>;
給出的typedef和別名聲明(alias declaration)做的是同樣的事情,這值得我們去考慮這里是否有什么科學的原因來讓我們更偏愛其中一個。
是的,但是在我說明之前,我想談一下別的:很多人發現在涉及函數指針的時候,別名聲明(alias declaration)更好接受一點:
//FP 是指向一個函數的指針的別名,這個函數以一個int和一個
//const std::string&為參數,不返回任何東西。
typedef void (*FP)(int, const std::string&); //typedef
//和上面同樣的意義
using FP = void(*)(int, const std::string&): //別名聲明
當然,這兩個都很容易接受,並且不管怎么說,很少人需要花費大量的時間學習使用函數指針類型的別名,所以這幾乎不是一個強硬的理由來讓我們選擇別名聲明(alias declaration)替換typedef。
但是強硬的理由是存在的:template。尤其是,別名聲明(alias declaration)能模板化(我們稱之為別名模板(alias template)),但是typedef不能這么做。這給了C++11程序員一個簡單的機制來表達在C++98中必須使用嵌套在模板化struct中的typedef來做的事情。舉個例子,考慮定義一個鏈表的別名,這個鏈表使用自定義的分配器(allocator)MyAlloc。使用別名聲明(alias declaration),這就是小case:
//MyAllocList<T>就是std::list<T, MyAlloc<T>>的別名
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
MyAllocList<Widget> lw; //客戶代碼
使用typedef,你就有麻煩了:
//MyAllocList<T>::type就是std::list<T, MyAlloc<T>>的別名
template<typename T>
struct MyAllocList{
typedef std::list<T, MyAlloc<T>> type;
};
MyAllocList<Widget>::type lw; //客戶代碼
如果你想在template中使用typedef來達到 使用template參數作為鏈表的模板類型參數 來創建別名的目的,這將變得更糟糕,你必須在typedef前面加上typename:
//Widget中有一個MyAllocList<T>作為成員變量
template<typename T>
class Widget{
private:
typename MyAllocList<T>::type list;
...
};
這里,MyAllocList
如果MyAllocList被定義為別名模板(alias template),對於typename的需求就消失了(笨重的“::type”后綴也消失了):
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;
template<typename T>
class WidgetP
private:
MyAllocList<T> list; //沒有"typename",沒有"::type"
...
};
對於你,MyAllocList
另一方面,當編譯器在Widget template中看到MyAllocList
舉個例子,一些誤入歧途的靈魂可能像這樣被制造出來:
class Wine{...};
template<> //特化的MyAllocList
class MyAllocList<Wine>{ //特化的類型是Wine
private:
enum class WinType //關於"enum class"的信息
{ White, Red, Rose}; //請看Item 10
WineType type; //在這個類中,type是一個成員變量
...
};
就像你看到的,MyAllocList
如果你做過任何模板元編程(template metaprogramming (TMP)),你肯定碰到過這樣的需求:使用template類型參數創造修改后的類型。舉個例子,對於給定的類型T,你可能想要去掉T上的const或引用屬性。比如,你可能想要把const std::string&變成std::string。或者你可能想要增加const給一個類型,或者把它變成一個左值引用。比如,把Widget變成const Widget或者Widget&。(如果你不知道任何TMP,那太糟糕了,因為如果你想要成為一個真正厲害的C++程序員,你至少需要熟悉C++在這方面(TMP)的基礎。在Item 23和27中,你能看到TMP的例子,包含我提及的類型轉換。)
在頭文件<type_traits>的各種模板中,C++11給了你工具,讓你以type traits的形式來做這些轉換。頭文件中有許多type traits,但不是全都用來做類型轉換的,但是它提供一些可預測的接口,給出一個你想轉換的類型T,結果的類型就是std::transformation
std::remove_const<T>::type //用 const T 產生 T
std::remove_reference<T>::type //用 T& 或 T&& 產生 T
std::add_lvalue_reference<T>::type //用 T 產生 T&
注釋只是總結了這些轉換做了什么,所以使用時不要過於隨便。在工程中使用它們前,我知道你會先看一下參考手冊的。
不管怎么說,我的目的不是給你一個type traits的引導,而是強調這些轉換在使用時需要在后面寫上“::type”。如果你在template中把它們應用於template類型參數(你常常需要在代碼中這樣使用),你還要在前面加上typename。原因是,在C++11的type traits中,這些語法使用嵌套於模板化struct中的typedef使用。好的,就是因為這樣的別名實現的技術,我想讓你知道它不如別名模板(alias template)!;
C++11中這么實現是有歷史原因的,但是我們不去討論它(我保證這很無聊),因為C++標准委員會很遲才認識到alias templates才是最好的做法,並且對於C++11中的所有轉換,他們在C++14中包含了所有的alias template版本。所有的別名都有一樣的形式:每個C++11的轉換std::transformation
std::remove_const<T>::type //C++11: const T -> T
std::remove_const_t<T> //C++14 等價的操作
std::remove_reference<T>::type // C++11: T&/T&& → T
std::remove_reference_t<T> // C++14 等價的操作
std::add_lvalue_reference<T>::type // C++11: T → T&
std::add_lvalue_reference_t<T> // C++14 等價的操作
C++11版本的轉換在C++14中仍然有效,但是我不知道你有什么理由去使用它們。甚至如果你沒有使用C++14,自己寫一份alias template就像玩一樣。只需要C++11的語言特性,甚至連小孩都能模仿這種模式,是吧?如果你碰巧有一份C++14標准的電子稿,將變得更加簡單,因為要做的事就只有拷貝和粘貼。這里,我給你一個開頭:
template <class T>
using remove_const_t = typename remove_const<T>::type;
template <class T>
using remove_reference_t = typename remove_reference<T>::type;
template <class T>
using add_lvalue_reference_t =
typename add_lvalue_reference<T>::type;
看到了嗎,沒有比這更簡單的事了。
你要記住的事
-
typedef不支持模板化,但是別名聲明(alias declaration)支持。
-
alias templates避免了“::type”后綴,以及在template中“typename”前綴(當代表一個類型時)的使用。
-
C++14提供所有C++11 type traits 轉換的alias templates版本。
