Item 9: 比起typedef更偏愛別名聲明(alias declaration)


本文翻譯自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 ::type 引用的是一個類型依賴於template的參數(T)。MyAllocList ::type 因此是一個依賴類型(dependent type),並且C++的眾多”可愛“的規則中的一個就是,在依賴類型前面必須加typename

如果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 (也就是,使用alias template)可能看起來和MyAllocList ::type (也就是,使用嵌套typedef)一樣,也依賴於template參數(T),但是你不是編譯器,當編譯器處理Widget template,並且碰到MyAllocList 的使用(也就是,使用alias template)時,它知道MyAllocList 就是類型的名字,因為MyAllocList是一個alias template:它肯定代表一個類型。MyAllocList 因此是一個非依賴類型(non-dependent type),並且它既不需要也不允許使用typename

另一方面,當編譯器在Widget template中看到MyAllocList ::type (也就是,使用嵌套typedef)時,編譯器不肯定它就代表一個類型,因為編譯器不知道這里是否有一個特化的MyAllocList,它的MyAllocList ::type 代表的不是一個類型。這聽起來很瘋狂,但是對於這個可能性,不要指責編譯器。我們人類知道怎么制造這樣的代碼。

舉個例子,一些誤入歧途的靈魂可能像這樣被制造出來:

class Wine{...};

template<>					//特化的MyAllocList
class MyAllocList<Wine>{	//特化的類型是Wine
private:
	enum class WinType		//關於"enum class"的信息
	{ White, Red, Rose};	//請看Item 10

	WineType type;			//在這個類中,type是一個成員變量
	...
};

就像你看到的,MyAllocList ::type 不是一個類型,如果WidgetWine來實例化,Widget template中的MyAllocList ::type 指向一個成員變量,而不是類型。因此,在Widget template中,MyAllocList ::type 是否代表一個類型需要依賴於T是什么,並且這也就是為什么如果你認為這是一個類型,編譯器還是堅持讓你在前面使用typename

如果你做過任何模板元編程(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 ::type(std::轉換 ::type):

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 ::type,在C++14中同樣有alias template以std::transformation_t命名。例子將解釋我說的:

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版本。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM