帶限定作用域的枚舉型別通過enum class
聲明,非限定作用域的枚舉型別通過enum
聲明。
1、非限定作用域的枚舉型別可能導致枚舉量泄漏到所在的作用域空間
namespace TestSpace {
enum Color {
red = 0,
green,
blue,
};
auto red = true; // 錯誤
}; // namespace TestSpace
而限定作用域的枚舉型別
namespace TestSpace {
enum class Color {
red = 0,
green,
blue,
};
auto red = true; // 沒問題
}; // namespace TestSpace
2.非限定作用域的枚舉型別存在一些隱式轉換
這個我不能說是不好的語法,我的意思是你知道自己代碼在干嘛。
比如這段代碼
#include <iostream>
namespace TestSpace {
enum Color {
red = 0,
green,
blue,
};
}; // namespace TestSpace
int main() {
using namespace TestSpace;
for (int i = 0; i < static_cast<int>(Color::blue); i++) {
std::cout << i << std::endl;
}
// or
Color color = Color::green;
if (1 < color < 2) {
std::cout << color << std::endl;
}
return 0;
}
這段代碼運行是沒問題的。
但是將在num
后加上class
,就不存在任何隱式轉換了。
如果你能控制非限定作用域的枚舉型別,做什么那么我覺得enum
和enum class
你喜歡就好。
#include <iostream>
namespace TestSpace {
class Buffer {
public:
enum Color {
red = 0,
green,
blue,
};
}; // class Buffer
}; // namespace TestSpace
using namespace TestSpace;
using Color = Buffer::Color;
int main() {
Color color = Color::green;
if (Color::red < color < Color::blue) {
std::cout << color << std::endl;
}
return 0;
}
反而這里如果在enum
后加上class
做判斷的時候還要加上強制類型轉換。那么就不自然了.
3. C++98不支持enum
進行前置聲明
enum Color; // 錯誤
enum class Coloe; // 正確
但是在c++11后enum
也可以前置聲明了,但是你必須設置它的底層型別。
舉個例子
enum Status {
good = 0;
failed = 1;
incomplete = 100;
indeterminate = 0xFFFFFFFF;
};
編譯器為了表示0
到0xFFFFFFFF
,通常會選用一個整數型別來表示Status的取值。
事實上為了節約內存,編譯器通常選用能夠表示枚舉類別取值的最小底層型別。這就說C++98編譯器只支持枚舉型別定義,可以保證枚舉型別被
使用前確定,底層型別是誰。
C++98枚舉類型前置聲明功能的缺乏可能導致導致編譯的依賴性。比如就是簡單的增加Status的一個值,可能就需要重新編譯整個工程。
enum class Status;
void Process(Status status); // C++98采用前置聲明的枚舉型別 ,如果Status發生改變,及時沒有影響Process的實現
// 也需要重新編譯整個文件
4. 為什么C++限定作用域枚舉就可以很大程度上避免這個問題呢?
其實我們已經知道這個問題的答案了,就是確定枚舉型別的底層型別。
C++默認限定作用域的底層型別是int。
當然你可能看到過這樣的代碼
enum class Status : std::uint32_t; // 這就是指定Status的底層型別
如果看到這里你就明白,為什么c++11不帶限定作用域的枚舉型別也可以前置聲明了。
enum Status : int; // 不帶限定作用的枚舉型別進行前置聲明。
5. 再舉個不帶限定作用域的優點吧
using UserInfo = std::tuple<std::string, // 姓名
std::string, // 電子郵件
std::size_t>; // 積分
auto val = std::get<1>(uInfo); // 這里我們用1來獲取get uInfo的中的電子郵件地址
但是實際上可能代碼迭代過很久后,你也不知道UserInfo中1就是代表電子郵件地址。
這時候我們就可添加一個不帶限定作用域的枚舉類型來輔助我們記住這個tuple。
using UserInfo = std::tuple<std::string, // 姓名
std::string, // 電子郵件
std::size_t>; // 積分
enum UserInfoFields {uiName, uiEmail, uiReputation}; // 關聯UserInfo,這個有點map的味道
auto val = std::get<uiEmail>(uInfo);
這就是利用不帶限定作用域的枚舉型別可以進行隱式轉換。
如果使用帶限定作用域的枚舉型別就需要進行強制類型轉換。
auto val = std::get<std::static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);
這樣寫出代碼就不太好看。
如果不這么寫也是可以的,簡單的說就是希望通過傳入一個枚舉變量得到一個std::size_t,其實就是得到尖括號中的值,
那么我就需要編譯時期確定。那么我們就寫一個模板函數吧。
template<typename E>
constexpr typename std::underlying_type<E>::type
toUType(E enumberator) noexcept {
return static_cast<typename std::underly_type<E>::type>(enumberator);
}
在c++14中就可以寫成這樣
template<typename E>
constexpr std::underlying_type_t<E>
toUType(E enumberator) noexcept {
return static_cast<std::underly_type_t<E>::type>(enumberator);
}
或者加個auto
template<typename E>
constexpr auto
toUType(E enumberator) noexcept {
return static_cast<std::underly_type_t<E>::type>(enumberator);
}
那么我們客戶段代碼就可以寫成
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uIndo);