簡體   English   中英

如何定義可擴展的 C++ 枚舉系統

[英]how to define an extensible C++ enum system

我在枚舉項目中遇到了問題。 在 EventDef.h 中,

enum EventDef {
    EVT1 = 0,
    EVT2,
    EVT3,
    EVT_NUM,
}

這樣,我可以在另一個頭文件 UIEventDef.h 中擴展 EventDef 系統

#include "EventDef.h"
enum UIEventDef {
    UIEVT1 = EVT_NUM,
    UIEVT2,
    UIEVT3,
}

但是,有一個限制,我不能在 NetEvent.h 中以相同的方式執行此操作。

#include "EventDef.h"
enum NetEventDef {
    NETEVT1 = EVT_NUM,
    NETEVT2,   //wrong: this will have the same value as UIEVT2
    NETEVT3,  
}

C++ 中是否有更好的編譯時解決方案,例如可以提供幫助的模板?

可擴展枚舉的想法本質上並不是“糟糕的設計”。 在其他語言中有它們的歷史,即使 c++ 不直接支持它們。 有不同種類的可擴展性。

可擴展枚舉對以下方面有用的事情

  • 錯誤代碼
  • 消息類型
  • 設備標識(OID 是類似系統的分層枚舉)

枚舉可擴展性示例

  • Objective Modula Two 具有可通過繼承等類進行擴展的枚舉。
  • Java 中的可擴展枚舉模式,可以用 C++ 實現。
  • Java 枚舉是可擴展的,因為額外的數據和方法可以是枚舉的一部分。
  • 在 C++ 中,typeid 運算符本質上是編譯器生成的帶有附加值的枚舉。

您在示例代碼中展示的那種可擴展性在獨立的 C++ 中沒有優雅的實現。 事實上,正如你所指出的,它很容易導致問題。

考慮一下您希望如何使用可擴展枚舉。 也許一組不可變的單例對象/映射將滿足您的需求。

在 C++ 中擁有可擴展枚舉的另一種方法是使用代碼生成器。 每個想要添加到可擴展枚舉的編譯單元都將 id 記錄在自己的、單獨的 .enum 文件中。 在構建時,在編譯之前,腳本(即 perl、bash 等)查找所有 .enum 文件,讀取它們,為每個 id 分配數值,並寫出一個頭文件,該文件與其他任何文件一樣包含在內。

為什么要這樣聲明事件枚舉? 如果你願意的話,讓它們“鏈接”你會得到什么,它們是你描述的方式?

我會讓它們完全獨立的枚舉。 其次,我建議您不要再使用舊樣式的枚舉。 c++11 在這里並且在 gcc 中可用。 您應該使用枚舉類:

enum class EventDef  : unsigned { Evt1 = 0, Evt2, Evt3, ... LastEvt }
enum class NetEvtDef : unsigned { NetEvt1 = 0, NetEvt2, NetEvt3, ... NetLastEvt }

如果您要切換,您可以這樣做:

void doSwitch(EventDef evt_def)
{
  switch(evt_def)
  {
    case EventDef::Evt1
    {
     // Do something;
     break;
    }
    default:
    // Do something;
  };
}

void doSwitch(NetEvtDef net_def)
{
  switch(net_def)
  {
    case NetEvtDef::NetEvt1
    {
     // Do something;
     break;
    }
    default:
    // Do something;
  };
}

通過為 doSwitch 創建重載函數,您可以隔離所有枚舉類型。 將它們放在不同的類別中是一種好處而不是問題。 它為您提供了以不同方式處理每個事件枚舉類型的靈活性。

按照您的描述將它們鏈接在一起會不必要地使問題復雜化。

我希望這有幫助。

我發現以下是復雜性、功能和類型安全之間的有用折衷。 它使用具有默認構造函數的自定義類的全局變量來簡化初始化。 下面的示例是一組可擴展的錯誤代碼。 您可能還想將名稱空間括起來(但我通常不會打擾)。

//
//  ErrorCodes.h
//  ExtendableEnum
//
//  Created by Howard Lovatt on 10/01/2014.
//

#ifndef ErrorCodes_h
#define ErrorCodes_h

#include <string>

class ErrorCodes {
public:
    static int nextValue_;
    explicit ErrorCodes(std::string const name) : value_{nextValue_++}, name_{name} {}
    ErrorCodes() : ErrorCodes(std::to_string(nextValue_)) {}
    int value() const { return value_; }
    std::string name() const { return name_; }
private:
    int const value_;
    std::string const name_;
    ErrorCodes(const ErrorCodes &);
    void operator=(const ErrorCodes &);
};

int ErrorCodes::nextValue_ = 0; // Weird syntax, does not declare a variable but rather initialises an existing one!
ErrorCodes first;
ErrorCodes second;
// ...

#endif


//
//  ExtraErrorCodes.h
//  ExtendableEnum
//
//  Created by Howard Lovatt on 10/01/2014.
//

#ifndef ExtraErrorCodes_h
#define ExtraErrorCodes_h

#include "ErrorCodes.h"

ErrorCodes extra{"Extra"};

#endif


//
//  ExtraExtraExtraCodes.h
//  ExtendableEnum
//
//  Created by Howard Lovatt on 10/01/2014.
//

#ifndef ExtendableEnum_ExtraExtraCodes_h
#define ExtendableEnum_ExtraExtraCodes_h

#include "ErrorCodes.h"

ErrorCodes extraExtra{"ExtraExtra"};

#endif


//
//  main.cpp
//  ExtendableEnum
//
//  Created by Howard Lovatt on 10/01/2014.
//

#include <iostream>
#include "ErrorCodes.h"
#include "ExtraErrorCodes.h"
#include "ExtraExtraErrorCodes.h"

// Need even more error codes
ErrorCodes const localExtra;

int main(int const notUsed, const char *const notUsed2[]) {
    std::cout << first.name() << " = " << first.value() << std::endl;
    std::cout << second.name() << " = " << second.value() << std::endl;
    std::cout << extra.name() << " = " << extra.value() << std::endl;
    std::cout << extraExtra.name() << " = " << extraExtra.value() << std::endl;
    std::cout << localExtra.name() << " = " << localExtra.value() << std::endl;
    return 0;
}

輸出是:

0 = 0
1 = 1
Extra = 2
ExtraExtra = 3
4 = 4

如果您有多個編譯單元,那么您需要使用單例模式的變體:

class ECs {
public:
    static ErrorCode & first() {
        static ErrorCode instance;
        return instance;
    }
    static ErrorCode & second() {
        static ErrorCode instance;
        return instance;
    }
private:
    ECs(ECs const&);
    void operator=(ECs const&);
};

我們可以在 C++ 中構造一個可擴展的“枚舉”,如下所示:

struct Last {};

struct D 
{
    using Next = Last;
    static const char* name = “D”;
};

struct C
{
    using Next = D;
    static const char* name = “C”;
};

struct B 
{
    using Next = C;
    static const char* name = “B”;
};

using First = B;

我們可以使用這些構造遍歷上述內容:

void Process(const B&)
{
    // do something specific for B
    cout << “Call me Ishmael” << endl;
}

template <class T>
void Process(const T&)
{
    // do something generic
    cout << “Call me “ << T::name << endl;
}

template <class T>
struct IterateThru
{
   static void iterate() 
   {
       Process(T());
       IterateThru<T::Next>::iterate();
   }
};

template <>
struct IterateThru<Last>
{
    static void iterate()
   {
       // end iteration
   }
};

遍歷“枚舉”:

IterateThru<First>::iterate();

擴展“枚舉”:

struct A
{
    using Next = B;
    static const char* name = “A”;
}:

using First = A:

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM