Item 9:優先使用別名宣告而非 typedef (Prefer Alias Declarations to typedefs)

Overview Table

主題 重點
基本功能 typedefalias declarationusing X = ...;)做完全相同的事:建立型別同義詞
決定性差異 alias declaration 可以模板化(成為 alias template);typedef 不行
typedef 的模板替代方案 只能把 typedef 巢狀塞進模板化 struct,使用時要寫 ::type
模板內使用巢狀 typedef dependent type,必須加 typename 前綴
模板內使用 alias template non-dependent typetypename 不需要也不允許
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 是什麼
例外:可讀性不是本 Item 的核心理由

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,在模板內還要 typenameC++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
例外:C++11 traits 在模板內仍需 typename

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>::typestd::transformation_t<T>
只有 C++11 能否用 _t 風格 能,自己以 alias template 包一層即可(純 C++11 特性)