<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                條款10:優先使用作用域限制的`enmus`而不是無作用域的`enum` ========================= 一般而言,在花括號里面聲明的變量名會限制在括號外的可見性。但是這對于`C++98`風格的`enums`中的枚舉元素并不成立。枚舉元素和包含它的枚舉類型同屬一個作用域空間,這意味著在這個作用域中不能再有同樣名字的定義: ```cpp enum Color { black, white, red}; // black, white, red 和 // Color 同屬一個定義域 auto white = false; // 錯誤!因為 white // 在這個定義域已經被聲明過 ``` 事實就是枚舉元素泄露到包含它的枚舉類型所在的作用域中,對于這種類型的`enum`官方稱作無作用域的(`unscoped`)。在`C++11`中對應的使用作用域的enums(`scoped enums`)不會造成這種泄露: ```cpp enum class Color { black, white, red}; // black, white, red // 作用域為 Color auto white = false; // fine, 在這個作用域內 // 沒有其他的 "white" Color c = white; // 錯誤!在這個定義域中 // 沒有叫"white"的枚舉元素 Color c = Color::white; // fine auto c = Color::white; // 同樣沒有問題(和條款5 // 的建議項吻合) ``` 因為限制作用域的`enum`是通過"enum class"來聲明的,它們有時被稱作枚舉類(`enum class`)。 限制作用域的`enum`可以減少命名空間的污染,這足以是我們更偏愛它們而不是不帶限制作用域的表親們。除此之外,限制作用域的`enums`還有一個令人不可抗拒的優勢:它們的枚舉元素可以是更豐富的類型。無作用域的`enum`會將枚舉元素隱式的轉換為整數類型(從整數出發,還可以轉換為浮點類型)。因此像下面這種語義上荒誕的情況是完全合法的: ```cpp enum Color { black, white, red }; // 無限制作用域的enum std::vector<std::size_t> // 返回x的質因子的函數 primeFactors(std::size_t x); Color c = red; ... if (c < 14.5 ){ // 將Color和double類型比較! auto factors = // 計算一個Color變量的質因子 primeFactors(c); } ``` 在`"enum"`后增加一個`"class"`,就可以將一個無作用域的`enum`轉換為一個有作用域的`enum`,變成一個有作用域的`enum`之后,事情就變得不一樣了。在有作用域的`enum`中不存在從枚舉元素到其他類型的隱式轉換: ```cpp enum class Color { black, white, red }; // 有作用域的enum Color c = Color::red; // 和前面一樣,但是 ... // 加上一個作用域限定符 if (c < 14.5){ // 出錯!不能將Color類型 // 和double類型比較 auto factors = // 出錯!不能將Color類型傳遞給 primeFactors(c); // 參數類型為std::size_t的函數 ... } ``` 如果你就是想將`Color`類型轉換為一個其他類型,使用類型強制轉換(`cast`)可以滿足你這種變態的需求: ```cpp if(static_cast<double>(c) < 14.5) { // 怪異但是有效的代碼 auto factors = // 感覺不可靠 primeFactors(static_cast<std::size_t(c)); // 但是可以編譯 ... } ``` 相較于無定義域的`enum`,有定義域的`enum`也許還有第三個優勢,因為有定義域的`enum`可以被提前聲明的,即可以不指定枚舉元素而進行聲明: ```cpp enum Color; // 出錯! enum class Color; // 沒有問題 ``` 這是一個誤導。在`C++11`中,沒有定義域的`enum`也有可能被提前聲明,但是需要一點額外的工作。這個工作時基于這樣的事實:`C++`中的枚舉類型都有一個被編譯器決定的潛在的類型。對于一個無定義域的枚舉類型像`Color`, ```cpp enum Color {black, white, red }; ``` 編譯器有可能選擇`char`作為潛在的類型,因為僅僅有三個值需要表達。然而一些枚舉類型有很大的取值的跨度,如下: ```cpp enum Status { good = 0, failed = 1, incomplete = 100, corrupt = 200, indeterminate = 0xFFFFFFFF }; ``` 這里需要表達的值范圍從`0`到`0xFFFFFFFF`。除非是在一個不尋常的機器上(在這臺機器上,`char`類型至少有`32`個`bit`),編譯器一定會選擇一個取值范圍比`char`大的整數類型來表示`Status`的類型。 為了更高效的利用內存,編譯器通常想為枚舉類型選擇可以充分表示枚舉元素的取值范圍但又占用內存最小的潛在類型。在某些情況下,為了代碼速度的優化,可以回犧牲內存大小,在那種情況下,編譯器可能不會選擇占用內存最小的可允許的潛在類型,但是編譯器依然希望能過優化內存存儲的大小。為了使這種功能可以實現,`C++98`僅僅支持枚舉類型的定義(所有枚舉元素被列出來),而枚舉類型的聲明是不被允許的。這樣可以保證在枚舉類型被用到之前,編譯器已經給每個枚舉類型選擇了潛在類型。 不能事先聲明枚舉類型有幾個不足。最引人注意的就是會增加編譯依賴性。再次看看`Status`這個枚舉類型: ```cpp enum Status { good = 0, failed = 1, incomplete = 100, corrupt = 200, indeterminate = 0xFFFFFFFF }; ``` 這個枚舉體可能會在整個系統中都會被使用到,因此被包含在系統每部分都依賴的一個頭文件當中。如果一個新的狀態需要被引入: ```cpp enum Status { good = 0, failed = 1, incomplete = 100, corrupt = 200, audited = 500, indeterminate = 0xFFFFFFFF }; ``` 就算一個子系統——甚至只有一個函數!——用到這個新的枚舉元素,有可能導致整個系統的代碼需要被重新編譯。這種事情是人們憎恨的。在`C++11`中,這種情況被消除了。例如,這里有一個完美的有效的有作用域的`enum`的聲明,還有一個函數將它作為參數: ```cpp enum class Status; // 前置聲明 void continueProcessing(Status s); // 使用前置聲明的枚舉體 ``` 如果`Status`的定義被修改,包含這個聲明的頭文件不需要重新編譯。更進一步,如果`Status`被修改(即,增加`audited`枚舉元素),但是`continueProcessing`的行為不受影響(因為`continueProcessing`沒有使用`audited`),`continueProcessing`的實現也不需要重新編譯。 但是如果編譯器需要在枚舉體之前知道它的大小,`C++11`的枚舉體怎么做到可以前置聲明,而`C++98`的枚舉體無法實現?原因是簡單的,對于有作用域的枚舉體的潛在類型是已知的,對于沒有作用域的枚舉體,你可以指定它。 對有作用域的枚舉體,默認的潛在的類型是`int`: ```cpp enum class Status; // 潛在類型是int ``` 如果默認的類型不適用于你,你可重載它: ```cpp enum class Status: std::uint32_t; // Status潛在類型是 // std::uint32_t // (來自<cstdint>) ``` 無論哪種形式,編譯器都知道有作用域的枚舉體中的枚舉元素的大小。 為了給沒有作用域的枚舉體指定潛在類型,你需要做相同的事情,結果可能是前置聲明: ```cpp enum Color: std::uint8_t; // 沒有定義域的枚舉體 // 的前置聲明,潛在類型是 // std::uint8_t ``` 潛在類型的指定也可以放在枚舉體的定義處: ```cpp enum class Status: std::uint32_t{ good = 0, failed = 1, incomplete = 100, corrupt = 200, audited = 500, indeterminate = 0xFFFFFFFF }; ``` 從有定義域的枚舉體可以避免命名空間污染和不易受無意義的隱式類型轉換影響的角度看,你聽到至少在一種情形下沒有定義域的枚舉體是有用的可能會感到驚訝。這種情況發生在引用`C++11`的`std::tuples`中的某個域時。例如,假設我們有一個元組,元組中保存著姓名,電子郵件地址,和用戶在社交網站的影響力數值: ```cpp using UserInfo = // 別名,參見條款9 std::tuple<std::string, // 姓名 std::string, // 電子郵件 std::size_t> ; // 影響力 ``` 盡管注釋已經說明元組的每部分代表什么意思,但是當你遇到像下面這樣的源代碼時,可能注釋沒有什么用: ```cpp UserInfo uInfo; // 元組類型的一個對象 ... auto val = std::get<1>(uInfo); // 得到第一個域的值 ``` 作為一個程序員,你有很多事要做。你真的想去記住元組的第一個域對應的是用戶的電子郵件地址?我不這么認為。使用一個沒有定義域的枚舉體來把名字和域的編號聯系在一起來避免去死記這些東西: ```cpp enum UserInfoFields {uiName, uiEmail, uiReputation }; UserInfo uInfo; // 和前面一樣 ... auto val = std::get<uiEmail>(uInfo); // 得到電子郵件域的值 ``` 上面代碼正常工作的原因是`UserInfoFields`到`std::get()`要求的`std::size_t`的隱式類型轉換。 如果使用有作用域的枚舉體的代碼就顯得十分冗余: ```cpp enum class UserInfoFields { uiName, uiEmail, uiReputaion }; UserInfo uInfo; // 和前面一樣 ... auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo); ``` 寫一個以枚舉元素為參數返回對應的`std::size_t`的類型的值可以減少這種冗余性。`std::get`是一個模板,你提供的值是一個模板參數(注意用的是尖括號,不是圓括號),因此負責將枚舉元素轉化為`std::size_t`的這個函數必須在編譯階段就確定它的結果。就像條款15解釋的,這意味著它必須是一個`constexpr`函數。 實際上,它必須是一個`constexpr`函數模板,因為它應該對任何類型的枚舉體有效。如果我們打算實現這種一般化,我們需要一般化返回值類型。不是返回`std::size_t`,我們需要返回枚舉體的潛在類型。通過`std::underlying_type`類型轉換來實現(關于類型轉換的信息,參見條款9)。最后需要將這個函數聲明為`noexcept`(參見條款14),因為我們知道它永遠不會觸發異常。結果就是這個函數模板可以接受任何的枚舉元素,返回這個元素的在編譯階段的常數值: ```cpp template<typename E> constexpr typename std::underlying_type<E>::type toUType(E enumerator) noexcept { return static_cast<typename std::underlying_type<E>::type>(enumerator); } ``` 在`C++14`中,`toUType`可以通過將`std::underlying_type<E>::type`替代為`std::underlying_type_t`(參見條款9): ```cpp template<typename E> // C++14 constexpr std::underlying_type_t<E> toUType(E enumerator) noexcept { return static_cast<std::underlying_type_t<E>>(enumerator); } ``` 更加優雅的`auto`返回值類型(參見條款3)在`C++14`中也是有效的: ```cpp template<typename E> constexpr auto toUType(E enumerator) noexcept { return static_cast<std::underlying_type_t<E>>(enumerator); } ``` 無論寫哪種形式,`toUType`允許我們想下面一樣訪問一個元組的某個域: ```cpp auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo); ``` 這樣依然比使用沒有定義域的枚舉體要復雜,但是它可以避免命名空間污染和不易引起注意的枚舉元素的的類型轉換。很多時候,你可能會決定多敲擊一些額外的鍵盤來避免陷入一個上古時代的枚舉體的技術陷阱中。 |要記住的東西| |:--------- | |`C++98`風格的`enum`是沒有作用域的`enum`| |有作用域的枚舉體的枚舉元素僅僅對枚舉體內部可見。只能通過類型轉換(`cast`)轉換為其他類型| |有作用域和沒有作用域的`enum`都支持指定潛在類型。有作用域的`enum`的默認潛在類型是`int`。沒有作用域的`enum`沒有默認的潛在類型。| |有作用域的`enum`總是可以前置聲明的。沒有作用域的`enum`只有當指定潛在類型時才可以前置聲明。|
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看