歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C++11 之 scoped enum

C++11 之 scoped enum

日期:2017/3/1 9:16:25   编辑:Linux編程

C++11 枚舉類型是“域化的” (scoped enum),相比 C++98 枚舉類型的“非域化” (unscoped enum),具有如下優點:

1 命名空間污染

一般來說,聲明在花括號內的名字,其可見性限制在由花括號定義的作用域內,但是非域化枚舉 (unscoped enum) 卻是例外

enum Color { black, white, red }; // black, white, red are in same scope as Color

auto white = false; // error! white already declared in this scope

C++11 域化枚舉 (scoped enum),關鍵字為 enum class,可視為一個 "class",故能防止“命名空間污染” (namespace pollution)

enum class Color { black, white, red }; // black, white, red are scoped to Color

auto white = false; // fine, no other "white" in scope

Color c = white; // error! no enumerator named "white" is in this scope

Color c = Color::white; // fine

auto c = Color::white; // also fine

2 強類型枚舉

非域化的枚舉成員,可以隱式的轉換為廣義整型 (integral types),如下所示:

enum Color { black, white, red }; // unscoped enum
std::vector<std::size_t> primeFactors(std::size_t x); // func. returning prime factors of x

Color c = red;

if (c < 14.5) // compare Color to double (!)
{ 
    auto factors = primeFactors(c); // compute prime factors of a Color (!)
    …
}

域化的枚舉成員,卻不可以隱式的轉換為廣義整型

enum class Color { black, white, red }; // enum is now scoped

Color c = Color::red; // as before, but with scope qualifier

if (c < 14.5)  // error! can't compare Color and double
{
    auto factors = primeFactors(c); // error! can't pass Color to function expecting std::size_t 
    …
}

正確的方式是使用 C++ 的類型轉換符 (cast)

if (static_cast<double>(c) < 14.5)     // odd code, but it's valid
{ 
    auto factors = primeFactors(static_cast<std::size_t>(c)); // suspect, but it compiles
    …
}

3 前置聲明

域化枚舉支持前置聲明 (forward-declared),也即可以不用初始化枚舉成員而聲明一個枚舉類型

enum class Color;

3.1 新增枚舉成員

非域化枚舉(unscoped enum) 在聲明時,編譯器會選擇占用內存最小的一種潛在類型 (underlying types),來代表每一個枚舉成員

enum Color { black, white, red };  // compiler may choose char type

下面的例子中,編譯器可能會選擇更大的能夠包含 0 ~ 0xFFFFFFFF 范圍的潛在類型

enum Status { 
    good = 0,
    failed = 1,
    incomplete = 100,
    corrupt = 200,
    indeterminate = 0xFFFFFFFF
};

非前置聲明的缺點在於,當增加一個新的枚舉成員時 (如下例的 audited ),整個系統將會被重新編譯一遍,即使只有一個很簡單的函數使用了新的枚舉成員 (audited)

enum Status { 
    good = 0,
    failed = 1,
    incomplete = 100,
    corrupt = 200,
    audited = 500,
    indeterminate = 0xFFFFFFFF
};

而使用前置聲明,當新增枚舉成員時,包含這些聲明的頭文件並不需要重新編譯,源文件則根據新枚舉成員的使用情況來決定是否重新編譯

如下例,Status 中新增枚舉成員 audited,如果函數 continuteProcesing 沒有使用 audited,則函數 continuteProcesing 的實現並不需要重新編譯

enum class Status; // forward declaration
void continueProcessing(Status s); // use of fwd-declared enum

3.2 潛在類型

域化枚舉的潛在類型 (underlying type),缺省為 int 型,當然也可以自行定義潛在類型。不管哪種方式,編譯器都會預先知道枚舉成員的大小

enum class Status; // underlying type is int

enum class Status: std::uint32_t; // underlying type for Status is std::uint32_t (from <cstdint>)

enum class Status: std::uint32_t  // specify underlying type on enum's definition
{ good = 0, failed = 1, incomplete = 100, corrupt = 200, audited = 500, indeterminate = 0xFFFFFFFF };

4 std::tuple

一般而言,使用 C++11 域化枚舉 (scoped enum) 是比較好的選擇,但 C++98 非域化枚舉 (unscoped enum) 也並非一無是處

4.1 非域化枚舉

當涉及到 std::tuple 時,使用 C++98 非域化枚舉反而會有優勢,如下例所示

假定一個社交網站中,每一位用戶,都使用一種模板類型 - 元組 (tuple) 來包含名字、郵箱、聲望值 (name, email, reputation value)

using UserInfo = std::tuple<std::string, std::string, std::size_t> ; // type alias

當以下代碼在另一個不同的源文件裡時 (source file),很有可能忘了元組 (tuple) 的第一個成員到底是名字還是郵箱 (name or email)

UserInfo uInfo; // object of tuple type
 ...

auto val = std::get<1>(uInfo); // get value of field 1

但是,使用非域化枚舉 (unscoped enum),可以不用擔心忘記了元組內的成員順序

enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo; // as before
…
auto val = std::get<uiEmail>(uInfo); // get value of email field

4.2 域化枚舉

上例中,假如使用域化枚舉 (scoped enum),則會用到類型轉換,看起來比較繁瑣

enum class UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo; // as before
…
auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);

可以使用一個模板函數,將枚舉成員 UserInfoFields::uiEmail 和 std::size_t 類型聯系起來

template<typename E>
constexpr typename std::underlying_type<E>::type toUType(E enumerator) noexcept
{
    return static_cast<typename std::underlying_type<E>::type>(enumerator);
}

這樣,便可以稍微縮減了代碼的復雜度。但是相比於非域化枚舉,看起來還是有些繁瑣

auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);

小結:

1) C++98 枚舉類型是“非域化的”;而C++11 枚舉類型是“域化的”,枚舉成員只在域內可見

2) 域化枚舉的缺省潛在類型 (underlying type) 是 int 型,而非域化枚舉沒有缺省潛在類型

3) 域化枚舉一般總是前置聲明,而非域化枚舉只有在指定了潛在類型時才可以是前置聲明

參考資料:

<Effective Modern C++> Item 10

Copyright © Linux教程網 All Rights Reserved