문제

C ++의 내장 열거는 TypeSafe가 아니라는 것이 상용입니다. 나는 어떤 클래스를 유형의 열거를 구현하는 클래스가 거기에 사용되는지 궁금했다.

typeafeenum.h :

struct TypesafeEnum
{
// Construction:
public:
    TypesafeEnum(): id (next_id++), name("") {}
    TypesafeEnum(const std::string& n): id(next_id++), name(n) {}

// Operations:
public:
    bool operator == (const TypesafeEnum& right) const;
    bool operator != (const TypesafeEnum& right) const;
    bool operator < (const TypesafeEnum& right) const;

    std::string to_string() const { return name; }

// Implementation:
private:
    static int next_id;
    int id;
    std::string name;
};

typeafeenum.cpp :

int TypesafeEnum::next_id = 1;

bool TypesafeEnum::operator== (const TypesafeEnum& right) const 
{ return id == right.id; }

bool TypesafeEnum::operator!= (const TypesafeEnum& right) const 
{ return !operator== (right); }

bool TypesafeEnum::operator< (const TypesafeEnum& right) const  
{ return id < right.id; }

용법:

class Dialog 
{
 ...
    struct Result: public TypesafeEnum
    {
        static const Result CANCEL("Cancel");
        static const Result OK("Ok");
    };


    Result doModal();
 ...
};

const Dialog::Result Dialog::Result::OK;
const Dialog::Result Dialog::Result::CANCEL;

덧셈:요구 사항에 대해 더 구체적이어야한다고 생각합니다. 나는 그들을 요약하려고 노력할 것이다 :

우선 순위 1 : 열거 변수를 유효하지 않은 값으로 설정하는 것은 예외없이 불가능합니다 (컴파일 타임 오류).

우선 순위 2 : 열거 값을 단일 명시 적 함수/메소드 호출로 INT로/로 변환 할 수 있어야합니다.

우선 순위 3 : 가능한 한 작고 우아하며 편리한 선언 및 사용법으로

우선 순위 4 : 열거 값을 문자열로 전환합니다.

우선 순위 5 : (좋은) 열거 값을 반복 할 가능성.

도움이 되었습니까?

해결책

나는 현재 Boost.enum 제안을 가지고 놀고 있습니다. 활도를 부스트하십시오 (파일 이름 enum_rev4.6.zip). 공식적으로 부스트에 포함시키기 위해 공식적으로 제출되지는 않았지만, 그것은 가능성이 있습니다. (문서화는 부족하지만 Clear 소스 코드 및 우수한 테스트로 구성됩니다.)

boost.enum은 다음과 같은 열거를 선언 할 수 있습니다.

BOOST_ENUM_VALUES(Level, const char*,
    (Abort)("unrecoverable problem")
    (Error)("recoverable problem")
    (Alert)("unexpected behavior")
    (Info) ("expected behavior")
    (Trace)("normal flow of execution")
    (Debug)("detailed object state listings")
)

자동으로 확장되도록하십시오.

class Level : public boost::detail::enum_base<Level, string>
{
public:
    enum domain
    {
        Abort,
        Error,
        Alert,
        Info,
        Trace,
        Debug,
    };

    BOOST_STATIC_CONSTANT(index_type, size = 6);

    Level() {}
    Level(domain index) : boost::detail::enum_base<Level, string>(index) {}

    typedef boost::optional<Level> optional;
    static optional get_by_name(const char* str)
    {
        if(strcmp(str, "Abort") == 0) return optional(Abort);
        if(strcmp(str, "Error") == 0) return optional(Error);
        if(strcmp(str, "Alert") == 0) return optional(Alert);
        if(strcmp(str, "Info") == 0) return optional(Info);
        if(strcmp(str, "Trace") == 0) return optional(Trace);
        if(strcmp(str, "Debug") == 0) return optional(Debug);
        return optional();
    }

private:
    friend class boost::detail::enum_base<Level, string>;
    static const char* names(domain index)
    {
        switch(index)
        {
        case Abort: return "Abort";
        case Error: return "Error";
        case Alert: return "Alert";
        case Info: return "Info";
        case Trace: return "Trace";
        case Debug: return "Debug";
        default: return NULL;
        }
    }

    typedef boost::optional<value_type> optional_value;
    static optional_value values(domain index)
    {
        switch(index)
        {
        case Abort: return optional_value("unrecoverable problem");
        case Error: return optional_value("recoverable problem");
        case Alert: return optional_value("unexpected behavior");
        case Info: return optional_value("expected behavior");
        case Trace: return optional_value("normal flow of execution");
        case Debug: return optional_value("detailed object state listings");
        default: return optional_value();
        }
    }
};

그것은 당신이 나열하는 우선 순위를 모두 만족시킵니다.

다른 팁

좋은 타협 방법은 다음과 같습니다.

struct Flintstones {
   enum E {
      Fred,
      Barney,
      Wilma
   };
};

Flintstones::E fred = Flintstones::Fred;
Flintstones::E barney = Flintstones::Barney;

버전이 같은 의미에서는 유형이 아니지만 사용량은 표준 열거보다 좋으며 필요할 때 여전히 정수 변환을 활용할 수 있습니다.

나는 사용한다 C ++ 0x TypeSafe 열거. 문자열 기능을 제공하는 헬퍼 템플릿/매크로를 사용합니다.

enum class Result { Ok, Cancel};

나는 아니에요. 적은 혜택을 얻기에는 너무 많은 오버 헤드입니다. 또한 직렬화를 위해 다른 데이터 유형에 열거 할 수있는 것은 매우 편리한 도구입니다. "타입 안전"열거가 C ++가 이미 충분한 구현을 제공하는 오버 헤드와 복잡성의 가치가있는 인스턴스를 본 적이 없습니다.

내 생각은 문제를 발명 한 다음 솔루션을 장착하고 있다는 것입니다. 나는 값 열거를 위해 정교한 프레임 워크를 수행 할 필요가 없습니다. 당신이있는 경우 헌신적인 값이 특정 세트의 구성원 만 있으면 고유 한 세트 데이터 유형의 변형을 해킹 할 수 있습니다.

나는 개인적으로 적응 된 버전의 버전을 사용하고 있습니다 TypeSafe Enum 관용구. 편집에서 언급 한 5 가지 "요구 사항"을 모두 제공하지는 않지만 어쨌든 그 중 일부는 강력하게 동의하지 않습니다. 예를 들어, PRIO#4 (값 변환)가 유형 안전과 어떻게 관련이 있는지 알 수 없습니다. 개별 값의 대부분의 시간 문자열 표현은 어쨌든 유형의 정의와 분리되어야합니다 (간단한 이유로 i18n을 생각하십시오). Prio#5 (선택 사항 인 iteratio)는 내가보고 싶은 가장 좋은 것 중 하나입니다. 당연히 열거적으로 일어나기 때문에 나는 그것이 당신의 요청에서 "선택적"으로 보인다는 것을 슬프게 느꼈지만, 그것은 A를 통해 더 잘 해결되는 것 같습니다. 별도의 반복 시스템 ~와 같은 begin/end 함수 또는 enum_iterator는 STL 및 C ++ 11 Foreach에서 원활하게 작동하게합니다.

Otoh이 간단한 관용구는 PRIO#3 PRIO#1을 멋지게 제공합니다. enum더 많은 유형 정보가 있습니다. 말할 것도없이, 대부분 외부 의존성 헤더가 필요하지 않은 매우 간단한 솔루션이므로 휴대하기가 매우 쉽습니다. 또한 열거 된 A-LA-C ++ 11을 범위로 만드는 이점이 있습니다.

// This doesn't compile, and if it did it wouldn't work anyway
enum colors { salmon, .... };
enum fishes { salmon, .... };

// This, however, works seamlessly.
struct colors_def { enum type { salmon, .... }; };
struct fishes_def { enum type { salmon, .... }; };

typedef typesafe_enum<colors_def> colors;
typedef typesafe_enum<fishes_def> fishes;

솔루션이 제공하는 유일한 "구멍"은 enum다른 유형의 s (또는 an enum 직접 값을 사용할 때 직접 비교되는 int) int:

if (colors::salmon == fishes::salmon) { .../* Ooops! */... }

그러나 지금까지 나는 컴파일러와 더 나은 비교를 제공함으로써 그러한 문제를 해결할 수 있음을 발견했습니다. 예를 들어, 두 가지 다른 두 가지를 비교하는 연산자를 명시 적으로 제공합니다. enum 유형, 그런 다음 실패하도록 강요합니다.

// I'm using backports of C++11 utilities like static_assert and enable_if
template <typename Enum1, typename Enum2>
typename enable_if< (is_enum<Enum1>::value && is_enum<Enum2>::value) && (false == is_same<Enum1,Enum2>::value) , bool >
::type operator== (Enum1, Enum2) {
    static_assert (false, "Comparing enumerations of different types!");
}

지금까지 코드를 깨뜨리지 않는 것처럼 보이지만 다른 일을하지 않고 특정 문제를 명시 적으로 다루는 것이 좋습니다.~해야 한다"그렇게 (나는 그것이 방해 할 것 같다 enumS는 이미 전환 연산자가 다른 곳에서 선언 한 것에 참여하고 있습니다. 나는 이것에 대해 기꺼이 논평을 받고있다).

이것을 위의 Typeafe Idiom과 결합하면 C ++ 11에 비교적 가까운 무언가가 제공됩니다. enum class 너무 모호한 일을하지 않고도 인간성 (가독성과 유지 가능성). 그리고 나는 그것이 재미 있다는 것을 인정해야한다. 나는 실제로 생각하지 않았다. 물어보기 내가 다루고있는 경우 컴파일러 enums 여부 ...

나는 자바를 생각합니다 enum 따라야 할 좋은 모델이 될 것입니다. 본질적으로 Java 형태는 다음과 같습니다.

public enum Result {
    OK("OK"), CANCEL("Cancel");

    private final String name;

    Result(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Java 접근 방식의 흥미로운 점은 그 것입니다 OK 그리고 CANCEL 불변의 싱글 톤 사례입니다 Result (당신이 보는 방법과 함께). 더 이상 인스턴스를 만들 수 없습니다 Result. 그들은 싱글 톤이므로 포인터/참조로 비교할 수 있습니다. :-)

ETA : Java에서는 손으로 비트 마스크를하는 대신 EnumSet 비트 세트를 지정하려면 (IT를 구현합니다 Set 인터페이스 및 세트와 같이 작동하지만 비트 마스크를 사용하여 구현). 손으로 쓴 비트 마스크 조작보다 훨씬 더 읽기 쉬운!

나는 이것에 대한 답을 주었다 여기, 다른 주제에. 원래 열거 정의를 수정할 필요없이 동일한 기능의 대부분을 허용하는 다른 스타일의 접근 방식입니다 (결과적으로 열거를 정의하지 않는 경우 사용량을 허용합니다). 또한 런타임 범위를 확인할 수 있습니다.

내 접근 방식의 단점은 열거와 도우미 클래스의 커플 링을 프로그래밍 방식으로 시행하지 않으므로 병렬로 업데이트되어야한다는 것입니다. 그것은 나를 위해 작동하지만 ymmv.

나는 현재 내 자신의 Typesafe Enum 라이브러리를 작성하고 있습니다. https://bitbucket.org/chopsii/typesafe-enums

나는 가장 경험이 풍부한 C ++ 개발자는 아니지만 Boost Vault Enums의 단점으로 인해 이것을 쓰고 있습니다.

자유롭게 확인하고 직접 사용하십시오. 그러나 그들은 (희망적으로 작은) 유용성 문제가 있으며 아마도 전혀 크로스 플랫폼이 아닐 수도 있습니다.

원한다면 기여하십시오. 이것은 나의 첫 번째 오픈 소스 사업입니다.

사용 boost::variant!

위의 많은 아이디어를 시도하고 부족한 아이디어를 찾은 후 나는이 간단한 접근 방식을 쳤다.

#include <iostream>
#include <boost/variant.hpp>

struct A_t {};
static const A_t A = A_t();
template <typename T>
bool isA(const T & x) { if(boost::get<A_t>(&x)) return true; return false; }

struct B_t {};
static const B_t B = B_t();
template <typename T>
bool isB(const T & x) { if(boost::get<B_t>(&x)) return true; return false; }

struct C_t {};
static const C_t C = C_t();
template <typename T>
bool isC(const T & x) { if(boost::get<C_t>(&x)) return true; return false; }

typedef boost::variant<A_t, B_t> AB;
typedef boost::variant<B_t, C_t> BC;

void ab(const AB & e)
{
  if(isA(e))
    std::cerr << "A!" << std::endl;
  if(isB(e))
    std::cerr << "B!" << std::endl;
  // ERROR:
  // if(isC(e))
  //   std::cerr << "C!" << std::endl;

  // ERROR:
  // if(e == 0)
  //   std::cerr << "B!" << std::endl;
}

void bc(const BC & e)
{
  // ERROR:
  // if(isA(e))
  //   std::cerr << "A!" << std::endl;

  if(isB(e))
    std::cerr << "B!" << std::endl;
  if(isC(e))
    std::cerr << "C!" << std::endl;
}

int main() {
  AB a;
  a = A;
  AB b;
  b = B;
  ab(a);
  ab(b);
  ab(A);
  ab(B);
  // ab(C); // ERROR
  // bc(A); // ERROR
  bc(B);
  bc(C);
}

아마도 매크로를 만들어 보일러 플레이트를 생성 할 수 있습니다. (그렇다면 알려주세요.)

다른 접근 방식과 달리 이것은 실제로 유형-안전하고 오래된 C ++에서 작동합니다. 당신은 멋진 유형을 만들 수도 있습니다 boost::variant<int, A_t, B_t, boost::none>, 예를 들어, A, B, 정수 또는 거의 haskell98 수준의 유형 안전 수준 일 수있는 값을 나타냅니다.

알아야 할 단점 :

  • Old Boost와의 제한-나는 1.33 부스트의 시스템에 있습니다. 당신은 당신의 변종의 20 개 항목으로 제한됩니다. 그러나 근로가 있습니다
  • 컴파일 시간에 영향을 미칩니다
  • 미친 오류 메시지 - 그러나 그것은 당신을위한 C ++입니다.

업데이트

여기에서 편의를 위해 TypeSafe-enum "Library"가 있습니다. 이 헤더를 붙여 넣으십시오.

#ifndef _TYPESAFE_ENUMS_H
#define _TYPESAFE_ENUMS_H
#include <string>
#include <boost/variant.hpp>

#define ITEM(NAME, VAL) \
struct NAME##_t { \
  std::string toStr() const { return std::string( #NAME ); } \
  int toInt() const { return VAL; } \
}; \
static const NAME##_t NAME = NAME##_t(); \
template <typename T> \
bool is##NAME(const T & x) { if(boost::get<NAME##_t>(&x)) return true; return false; } \


class toStr_visitor: public boost::static_visitor<std::string> {
public:
  template<typename T>
  std::string operator()(const T & a) const {
    return a.toStr();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
std::string toStr(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toStr_visitor(), a);
}

class toInt_visitor: public boost::static_visitor<int> {
public:
  template<typename T>
  int operator()(const T & a) const {
    return a.toInt();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
int toInt(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toInt_visitor(), a);
}

#define ENUM(...) \
typedef boost::variant<__VA_ARGS__>
#endif

그리고 다음과 같이 사용하십시오.

ITEM(A, 0);
ITEM(B, 1);
ITEM(C, 2);

ENUM(A_t, B_t) AB;
ENUM(B_t, C_t) BC;

당신은 말해야합니다 A_t 대신에 A 에서 ENUM 마법의 일부를 파괴하는 매크로. 오, 음. 또한 이제 a가 있습니다 toStr 기능과 a toInt OPS를 충족하는 기능 스트링 및 int로 간단한 변환의 요구 사항. 내가 알아낼 수없는 요구 사항은 항목을 반복하는 방법입니다. 그런 것을 쓰는 방법을 알고 있다면 알려주세요.

이 게시물이 너무 늦었는지 확실하지 않지만 gamedev.net에 5 번째 지점을 제외한 모든 것을 만족시키는 기사가 있습니다 (열거 자보다 반복 할 수있는 능력).http://www.gamedev.net/reference/snippets/features/cppstringizing/

이 기사에서 설명하는 방법은 문자열 변환 지원을 허용합니다. 기존 열거 코드를 변경하지 않고. 그래도 새로운 열거에 대한 지원 만 원한다면 Boost.enum (위에서 언급)으로 갈 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top