Item 9:優先使用別名宣告而非 typedef (Prefer Alias Declarations to typedefs)
Overview Table
| 主題 | 重點 |
|---|---|
| 基本功能 | typedef 與 alias declaration(using X = ...;)做完全相同的事:建立型別同義詞 |
| 決定性差異 | alias declaration 可以模板化(成為 alias template);typedef 不行 |
| typedef 的模板替代方案 | 只能把 typedef 巢狀塞進模板化 struct,使用時要寫 ::type |
| 模板內使用巢狀 typedef | 是 dependent type,必須加 typename 前綴 |
| 模板內使用 alias template | 是 non-dependent type,typename 不需要也不允許 |
| type traits 演進 | C++11:std::transformation<T>::type;C++14:std::transformation_t<T>(alias template 版本) |
using vs typedef:語法對照
兩者語意相同,但 using 在複雜型別(尤其函式指標)上可讀性較佳:
// 冗長型別的同義詞:兩種寫法等價
typedef
std::unique_ptr<std::unordered_map<std::string, std::string>>
UPtrMapSS; // C++98 風格
using UPtrMapSS =
std::unique_ptr<std::unordered_map<std::string, std::string>>; // C++11 alias declaration
// 函式指標型別:alias declaration 更直覺(名字在左、型別在右)
typedef void (*FP)(int, const std::string&); // typedef:名字埋在中間
using FP = void (*)(int, const std::string&); // alias:一眼看出 FP 是什麼
Meyers 明說函式指標的可讀性差異「不是選擇 alias declaration 的有力理由」——真正的決定性理由是模板化能力(見下節)。若面試只答「using 比較好讀」,等於沒答到重點。
Alias Template:typedef 做不到的事
alias declaration 可以模板化,typedef 不行。想為「使用自訂 allocator MyAlloc 的 linked list」定義同義詞:
// alias template:直接了當
template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>; // MyAllocList<T> 是
// std::list<T, MyAlloc<T>> 的同義詞
MyAllocList<Widget> lw; // 客戶端程式碼,乾淨俐落
// typedef 的 C++98 hack:巢狀在模板化 struct 裡
template<typename T>
struct MyAllocList {
typedef std::list<T, MyAlloc<T>> type; // 必須透過 ::type 存取
};
MyAllocList<Widget>::type lw; // 客戶端要多寫 ::type
差異在模板內部使用時更致命——巢狀 typedef 必須加 typename:
template<typename T>
class Widget {
private:
typename MyAllocList<T>::type list; // typedef 版:typename + ::type 缺一不可
};
template<typename T>
class Widget {
private:
MyAllocList<T> list; // alias template 版:無 typename、無 ::type
};
為什麼 typedef 版需要 typename?(dependent type)
MyAllocList<T>::type 依賴模板參數 T,是 dependent type;C++ 規定 dependent type 的名稱必須以 typename 宣告「這是型別」。編譯器無法確定 ::type 是型別,因為可能存在特化版本讓 ::type 不是型別:
class Wine { /* ... */ };
template<> // MyAllocList 對 Wine 的特化
class MyAllocList<Wine> {
private:
enum class WineType
{ White, Red, Rose };
WineType type; // 這裡 type 是「資料成員」,不是型別!
};
編譯器看到模板內的名稱時的判斷流程:
MyAllocList<T>::type ──► T 未定 ──► 可能有特化讓 type 不是型別
(巢狀 typedef) └─► dependent type ──► 必須寫 typename
MyAllocList<T> ──► MyAllocList 是 alias template,必定命名一個型別
(alias template) └─► non-dependent type ──► typename 不需要也不允許
Type Traits:C++11 的 ::type 與 C++14 的 _t
C++11 <type_traits> 的型別轉換 traits 是用「巢狀 typedef in 模板化 struct」實作的(正是本 Item 說較差的技術),因此使用時要 ::type,在模板內還要 typename。C++14 為所有 C++11 型別轉換補上了 alias template 版本,命名規則為尾綴 _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,也能自己寫出 _t 版本(只需 C++11 語言特性):
template<class T>
using remove_const_t = typename std::remove_const<T>::type; // 自製 C++14 風格 alias
std::remove_reference<T>::type 用在模板中必須寫成 typename std::remove_reference<T>::type;_t 版本則完全免除。另外並非 <type_traits> 裡所有 traits 都做「型別轉換」(如 std::is_same 是判斷式),_t 對應的只是轉換類 traits。
Exam/Test Patterns
| 情境關鍵字 | 答案 |
|---|---|
| typedef 和 using 的「唯一」功能差異 | alias declaration 可模板化(alias template);typedef 不行 |
| typedef 想模板化怎麼辦 | 巢狀在模板化 struct 內,透過 MyAllocList<T>::type 存取 |
模板內用 X<T>::type 報錯 |
缺 typename 前綴——X<T>::type 是 dependent type |
| 模板內 alias template 前加 typename | 錯誤:alias template 是 non-dependent type,typename 不需要也不允許 |
為何編譯器不確定 ::type 是型別 |
可能有特化(如 MyAllocList<Wine>)讓 ::type 是資料成員 |
std::remove_reference_t<T> 是什麼 |
C++14 對 C++11 trait 的 alias template,等於 typename std::remove_reference<T>::type |
| C++14 traits 命名規則 | std::transformation<T>::type → std::transformation_t<T> |
只有 C++11 能否用 _t 風格 |
能,自己以 alias template 包一層即可(純 C++11 特性) |
Related Notes
- 04-Moving-To-Modern-Cpp/02-Nullptr — Item 8 的
MuxGuard範例即以 alias declaration 定義(using MuxGuard = std::lock_guard<std::mutex>;) - 04-Moving-To-Modern-Cpp/04-Scoped-Enums — Item 10 的
toUType使用std::underlying_type_t,正是本 Item 介紹的 C++14_talias template - 02-Deducing-Types/01-Template-Type-Deduction — type traits 的轉換以模板型別推導的規則為基礎(如
remove_reference對應 Item 1 的參考剝除) - 06-Move-Semantics-And-Perfect-Forwarding/01-Std-Move-And-Std-Forward —
std::move的實作即依賴std::remove_reference_t,是 type traits 的實戰應用