không gian tên cho các loại enum - các phương pháp hay nhất


102

Thông thường, người ta cần nhiều kiểu liệt kê cùng nhau. Đôi khi, người ta có một cuộc đụng độ tên tuổi. Có hai giải pháp cho vấn đề này: sử dụng không gian tên hoặc sử dụng tên phần tử enum 'lớn hơn'. Tuy nhiên, giải pháp không gian tên có hai cách triển khai khả thi: một lớp giả với các enum lồng nhau hoặc một không gian tên hoàn chỉnh.

Tôi đang tìm kiếm ưu và nhược điểm của cả ba cách tiếp cận.

Thí dụ:

// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cColorBlue: //...
        //...
    }
 }


// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
 }


 // a real namespace?
 namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
 namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
 void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }

19
Trước hết, tôi sẽ sử dụng màu :: Đỏ, Feeling: Angry, vv
abatishchev

câu hỏi hay, tôi sử dụng phương pháp namespace ....;)
MiniScalope

18
tiền tố "c" trên mọi thứ ảnh hưởng đến khả năng đọc.
Người dùng

8
@User: tại sao, cảm ơn bạn đã mở cuộc thảo luận tiếng Hungary :)
xtofl

5
Lưu ý rằng bạn không cần đặt tên cho enum như trong enum e {...}, enum có thể ẩn danh, tức là enum {...}, điều này có ý nghĩa hơn nhiều khi được bao bọc trong không gian tên hoặc lớp.
kralyk

Câu trả lời:


73

Câu trả lời gốc C ++ 03:

Các lợi ích từ một namespace(hơn một class) là bạn có thể sử dụng usingtờ khai khi bạn muốn.

Các vấn đề với việc sử dụng một namespacelà không gian tên có thể được mở rộng ở những nơi khác trong các mã. Trong một dự án lớn, bạn sẽ không được đảm bảo rằng hai môi trường khác nhau đều không nghĩ rằng chúng được gọi làeFeelings

Đối với mã trông đơn giản hơn, tôi sử dụng a struct, vì bạn có lẽ muốn nội dung được công khai.

Nếu bạn đang thực hiện bất kỳ phương pháp nào trong số này, bạn đã đi trước đường cong và có thể không cần xem xét kỹ lưỡng điều này thêm.

Lời khuyên mới hơn, C ++ 11:

Nếu bạn đang sử dụng C ++ 11 trở lên, enum classsẽ ngầm định phạm vi các giá trị enum trong tên của enum.

Với enum classbạn, bạn sẽ mất các chuyển đổi và so sánh ngầm định với các loại số nguyên, nhưng trong thực tế, điều đó có thể giúp bạn phát hiện ra mã không rõ ràng hoặc có lỗi.


4
Tôi đồng ý với ý tưởng cấu trúc. Và cảm ơn vì lời khen :)
xtofl

3
+1 Tôi không thể nhớ cú pháp "enum class" trong C ++ 11. Nếu không có tính năng đó, enums không đầy đủ.
Grault

Là một tùy chọn của họ để sử dụng "using" cho phạm vi ngầm định của "enum class". ví dụ: Sẽ thêm 'using Color :: e;' để mã cho phép sử dụng 'cRed' và biết điều này phải là Màu :: e :: cRed?
JVApen

20

FYI Trong C ++ 0x có một cú pháp mới cho các trường hợp như những gì bạn đã đề cập (xem trang wiki C ++ 0x )

enum class eColors { ... };
enum class eFeelings { ... };

11

Tôi đã kết hợp các câu trả lời trước với một cái gì đó như thế này: (CHỈNH SỬA: Điều này chỉ hữu ích cho trước C ++ 11. Nếu bạn đang sử dụng C ++ 11, hãy sử dụng enum class)

Tôi có một tệp tiêu đề lớn chứa tất cả các enum dự án của tôi, bởi vì các enum này được chia sẻ giữa các lớp worker và không có ý nghĩa gì khi đặt các enums trong chính các lớp worker.

Các hàm này structtránh public: cú pháp sugar và typedefcho phép bạn thực sự khai báo các biến của các enum này trong các lớp worker khác.

Tôi không nghĩ rằng việc sử dụng một không gian tên sẽ giúp ích gì cả. Có lẽ điều này là do tôi là một lập trình viên C #, và ở đó bạn phải sử dụng tên kiểu enum khi tham chiếu các giá trị, vì vậy tôi đã quen với nó.

    struct KeySource {
        typedef enum { 
            None, 
            Efuse, 
            Bbram
        } Type;
    };

    struct Checksum {
        typedef enum {
            None =0,
            MD5 = 1,
            SHA1 = 2,
            SHA2 = 3
        } Type;
    };

    struct Encryption {
        typedef enum {
            Undetermined,
            None,
            AES
        } Type;
    };

    struct File {
        typedef enum {
            Unknown = 0,
            MCS,
            MEM,
            BIN,
            HEX
        } Type;
    };

...

class Worker {
    File::Type fileType;
    void DoIt() {
       switch(fileType) {
       case File::MCS: ... ;
       case File::MEM: ... ;
       case File::HEX: ... ;
    }
}

9

Tôi chắc chắn sẽ tránh sử dụng một lớp học cho việc này; sử dụng không gian tên để thay thế. Câu hỏi xoay quanh việc nên sử dụng không gian tên hay sử dụng id duy nhất cho các giá trị enum. Cá nhân tôi sử dụng một không gian tên để id của tôi có thể ngắn hơn và hy vọng sẽ dễ hiểu hơn. Sau đó, mã ứng dụng có thể sử dụng chỉ thị 'sử dụng không gian tên' và làm cho mọi thứ dễ đọc hơn.

Từ ví dụ của bạn ở trên:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}

Bạn có thể đưa ra gợi ý về lý do tại sao bạn thích một không gian tên hơn một lớp không?
xtofl

@xtofl: bạn không thể viết 'bằng cách sử dụng Màu sắc của lớp'
MSalters

2
@MSalters: Bạn cũng không thể viết Colors someColor = Red;vì không gian tên không tạo thành một kiểu. Colors::e someColor = Red;Thay vào đó, bạn sẽ phải viết , điều này khá phản trực quan.
SasQ 12/12/12

@SasQ Bạn sẽ không phải sử dụng Colors::e someColorngay cả với một struct/classnếu bạn muốn sử dụng nó trong một switchcâu lệnh? Nếu bạn sử dụng ẩn danh enumthì công tắc sẽ không thể đánh giá a struct.
Macbeth's Enigma

1
Xin lỗi, nhưng const e cdường như tôi khó đọc được :-) Đừng làm vậy. Tuy nhiên, sử dụng một không gian tên là tốt.
dhaumann

7

Sự khác biệt giữa việc sử dụng một lớp hoặc một không gian tên là lớp không thể được mở lại như một không gian tên có thể. Điều này tránh khả năng không gian tên có thể bị lạm dụng trong tương lai, nhưng cũng có một vấn đề là bạn cũng không thể thêm vào tập hợp các kiểu liệt kê.

Một lợi ích có thể có khi sử dụng một lớp, là chúng có thể được sử dụng làm đối số kiểu mẫu, điều này không đúng với không gian tên:

class Colors {
public:
  enum TYPE {
    Red,
    Green,
    Blue
  };
};

template <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

Cá nhân tôi không phải là người thích sử dụng và tôi thích những cái tên đủ tiêu chuẩn hơn, vì vậy tôi không thực sự coi đó là một điểm cộng cho không gian tên. Tuy nhiên, đây có lẽ không phải là quyết định quan trọng nhất mà bạn sẽ thực hiện trong dự án của mình!


Không mở lại các lớp học cũng là một bất lợi tiềm ẩn. Danh sách màu sắc cũng không phải là hữu hạn.
MSalters

1
Tôi nghĩ rằng việc không phải đăng ký lại một lớp là một lợi thế tiềm năng. Nếu tôi muốn nhiều màu hơn, tôi sẽ biên dịch lại lớp với nhiều màu hơn. Nếu tôi không thể làm điều này (nói rằng tôi không có mã), thì tôi không muốn chạm vào nó trong mọi trường hợp.
Thomas Eding

@MSalters: Không có khả năng mở lại một lớp không chỉ không phải là một bất lợi mà còn là một công cụ an toàn. Bởi vì khi có thể mở lại một lớp và thêm một số giá trị vào enum, nó có thể phá vỡ mã thư viện khác vốn đã phụ thuộc vào enum đó và chỉ biết tập giá trị cũ. Sau đó, nó sẽ vui vẻ chấp nhận những giá trị mới đó, nhưng phá vỡ trong thời gian chạy vì không biết phải làm gì với chúng. Hãy nhớ Nguyên tắc Mở-Đóng: Lớp nên được đóng để sửa đổi , nhưng mở để mở rộng . Bằng cách mở rộng, tôi có nghĩa là không thêm vào mã hiện có, nhưng bao bọc nó bằng mã mới (ví dụ: dẫn xuất).
SasQ 12/12/12

Vì vậy, khi bạn muốn mở rộng enum, bạn nên đặt nó thành một kiểu mới bắt nguồn từ kiểu đầu tiên (giá như nó có thể dễ dàng trong C ++ ...; /). Sau đó, nó có thể được sử dụng một cách an toàn bởi mã mới hiểu các giá trị mới đó, nhưng chỉ những giá trị cũ mới được chấp nhận (bằng cách chuyển đổi chúng xuống) bởi mã cũ. Họ không nên chấp nhận bất kỳ giá trị nào từ các giá trị mới đó là không đúng loại (giá trị mở rộng). Chỉ những giá trị cũ mà chúng hiểu được mới được chấp nhận là thuộc loại (cơ sở) chính xác (và vô tình cũng thuộc loại mới, vì vậy mã mới cũng có thể được chấp nhận).
SasQ 12/12/12

7

Lợi thế của việc sử dụng một lớp là bạn có thể xây dựng một lớp chính thức bên trên nó.

#include <cassert>

class Color
{
public:
    typedef enum
    {
        Red,
        Blue,
        Green,
        Yellow
    } enum_type;

private:
    enum_type _val;

public:
    Color(enum_type val = Blue)
        : _val(val)
    {
        assert(val <= Yellow);
    }

    operator enum_type() const
    {
        return _val;
    }
};

void SetPenColor(const Color c)
{
    switch (c)
    {
        case Color::Red:
            // ...
            break;
    }
}

Như ví dụ trên cho thấy, bằng cách sử dụng một lớp, bạn có thể:

  1. cấm (thật đáng buồn, không phải thời gian biên dịch) C ++ cho phép ép kiểu từ giá trị không hợp lệ,
  2. đặt mặc định (khác 0) cho enums mới tạo,
  3. thêm các phương thức khác, như để trả về một biểu diễn chuỗi của một lựa chọn.

Chỉ cần lưu ý rằng bạn cần khai báo operator enum_type()để C ++ biết cách chuyển đổi lớp của bạn thành enum cơ bản. Nếu không, bạn sẽ không thể chuyển loại cho một switchcâu lệnh.


Giải pháp này bằng cách nào đó có liên quan đến những gì được hiển thị ở đây không ?: en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum Tôi đang nghĩ cách làm cho nó trở thành một mẫu mà tôi không phải viết lại mẫu này mỗi lần Tôi cần sử dụng nó.
SasQ

@SasQ: nó có vẻ tương tự, vâng. Đó có lẽ là cùng một ý tưởng. Tuy nhiên, tôi không chắc liệu một mẫu có mang lại lợi ích hay không trừ khi bạn thêm nhiều phương pháp 'phổ biến' vào đó.
Michał Górny

1. Không thực sự đúng. Bạn có thể kiểm tra thời gian biên dịch để đảm bảo rằng enum hợp lệ, thông qua const_expr hoặc thông qua phương thức khởi tạo riêng cho một int.
xryl669

5

Vì enums được xác định phạm vi bao quanh của chúng, nên có lẽ tốt nhất bạn nên bọc chúng trong một thứ gì đó để tránh làm ô nhiễm không gian tên chung và tránh xung đột tên. Tôi thích một không gian tên hơn cho lớp đơn giản vì namespacecảm giác giống như một cái túi để đựng, trong khi classcảm thấy giống như một đối tượng mạnh mẽ (xem cuộc tranh luận structso với class). Một lợi ích có thể có đối với không gian tên là nó có thể được mở rộng sau này - hữu ích nếu bạn đang xử lý mã của bên thứ ba mà bạn không thể sửa đổi.

Tất nhiên, đây là điều tất nhiên khi chúng ta nhận được các lớp enum với C ++ 0x.


các lớp enum ... cần phải tra cứu điều đó!
xtofl

3

Tôi cũng có xu hướng quấn quýt trong các lớp học.

Như đã được Richard Corden báo hiệu, lợi ích của một lớp là nó là một kiểu theo nghĩa c ++ và vì vậy bạn có thể sử dụng nó với các mẫu.

Tôi có hộp công cụ đặc biệt :: Enum class cho nhu cầu của mình mà tôi chuyên về mọi mẫu cung cấp các chức năng cơ bản (chủ yếu: ánh xạ một giá trị enum thành một chuỗi std :: để I / O dễ đọc hơn).

Mẫu nhỏ của tôi cũng có thêm lợi ích là thực sự kiểm tra các giá trị được phép. Trình biên dịch hơi lỏng lẻo trong việc kiểm tra xem giá trị có thực sự nằm trong enum hay không:

typedef enum { False: 0, True: 2 } boolean;
   // The classic enum you don't want to see around your code ;)

int main(int argc, char* argv[])
{
  boolean x = static_cast<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

Tôi luôn làm phiền tôi rằng trình biên dịch sẽ không nắm bắt được điều này, vì bạn bị bỏ lại với một giá trị enum vô nghĩa (và bạn sẽ không mong đợi).

Tương tự:

typedef enum { Zero: 0, One: 1, Two: 2 } example;

int main(int argc, char* argv[])
{
  example y = static_cast<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

Một lần nữa main sẽ trả về lỗi.

Vấn đề là trình biên dịch sẽ phù hợp với enum trong biểu diễn nhỏ nhất có sẵn (ở đây chúng ta cần 2 bit) và mọi thứ phù hợp với biểu diễn này được coi là giá trị hợp lệ.

Cũng có một vấn đề là đôi khi bạn muốn có một vòng lặp trên các giá trị có thể có thay vì một công tắc để bạn không phải sửa đổi tất cả các công tắc mà bạn chuyển đổi mỗi khi bạn thêm một giá trị vào enum.

Nói chung, người trợ giúp nhỏ của tôi thực sự dễ dàng mọi thứ cho enum của tôi (tất nhiên, nó thêm một số chi phí) và nó chỉ có thể thực hiện được vì tôi lồng mỗi enum trong cấu trúc của riêng nó :)


4
Hấp dẫn. Bạn có phiền chia sẻ định nghĩa về lớp Enum của mình không?
momeara
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.