سؤال

ومن المعروف أن المدمج في enums في C++ ليست typesafe.كنت أتساءل أي الطبقات تنفيذ typesafe enums تستخدم هناك...انا عن نفسي استخدم ما يلي "دراجة" ، ولكن هذا هو إلى حد ما مطول و محدودة:

typesafeenum.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;
};

typesafeenum.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:تحويل قيمة تعداد من الباحث أن يكون ممكنا مع واحد صريح وظيفة/طريقة الاتصال.

الأولوية 3:صغيرة الحجم, أنيقة ومريحة الإعلان الاستخدام ممكن

الأولوية 4:تحويل التعداد القيم من السلاسل.

الأولوية 5:(من الجميل أن يكون) إمكانية تكرار عبر التعداد القيم.

هل كانت مفيدة؟

المحلول

أنا حاليا حول اللعب مع دفعة.التعداد اقتراح من دفعة قبو (اسم الملف enum_rev4.6.zip).على الرغم من أنه لم يكن رسميا المقدمة لإدراجها في تعزيز انها صالحة للاستعمال كما هو.(وثائق تفتقر ولكن يتم عن طريق مسح رمز مصدر جيد الاختبارات.)

دفعة.التعداد يتيح لك تعلن التعداد مثل هذا:

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;

ليس typesafe في نفس بمعنى أن الإصدار الخاص بك ولكن استخدام أجمل من معيار enums ، لا يزال بإمكانك الاستفادة من عدد صحيح التحويل عندما كنت في حاجة إليها.

يمكنني استخدام C++0x typesafe enums.يمكنني استخدام بعض قالب المساعد/وحدات الماكرو التي توفر من سلسلة وظائف.

enum class Result { Ok, Cancel};

لا.الكثير من النفقات العامة من أجل فائدة تذكر.أيضا ، أن تكون قادرة على الطائفة التعدادات إلى أنواع مختلفة من البيانات من أجل التسلسل هو أداة قوية جدا.لم أرى مثيل حيث نوع "آمنة" التعداد سوف يكون من المفيد النفقات العامة و التعقيد حيث C++ إقامة جيدة بما فيه الكفاية التنفيذ بالفعل.

رأيي هو أنك اختراع مشكلة ثم تركيب حل على ذلك.أنا أرى أنه لا حاجة إلى وضع إطار التعداد من القيم.إذا كنت مخصص إلى وجود القيم الخاصة بك فقط أعضاء في مجموعة معينة ، يمكن أن الإختراق حتى البديل من مجموعة فريدة نوع البيانات.

أنا شخصيا باستخدام نسخة معدلة من typesafe التعداد لغة.فإنه لا يوفر كل خمس "الشروط" التي كنت قد ذكرت في تحرير الخاص بك ولكن أنا لا أوافق بشدة مع بعض منهم على أي حال.على سبيل المثال, أنا لا أرى كيف Prio#4 (تحويل القيم إلى سلاسل) له علاقة مع نوع الأمان.معظم الوقت تمثيل سلسلة من القيم الفردية يجب أن تكون منفصلة عن تعريف نوع على أي حال (اعتقد i18n لسبب بسيط لماذا).Prio#5 (iteratio ، وهو اختياري) هي واحدة من أجمل الأشياء أود أن أرى بطبيعة الحال يحدث في enums لذا شعرت بالحزن أن يبدو "اختياري" في طلبك, ولكن يبدو أنه من الأفضل معالجتها عن طريق منفصلة التكرار النظام مثل begin/end وظائف أو enum_iterator ، مما يجعلها تعمل بسلاسة مع المحكمة الخاصة بلبنان و C++11 foreach.

OTOH هذا بسيط تعبير لطيف يوفر Prio#3 Prio#1 بفضل حقيقة أنه في الغالب إلا يلتف enums مع أكثر من نوع من المعلومات.ناهيك عن أنه حل بسيط جدا التي لا تتطلب أي تبعية خارجية رؤوس, لذلك فمن السهل جدا لتحملها.كما أن لديها ميزة إجراء التعدادات راقب 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;

فقط "الثقب" الذي يوفر الحل هو أنه لا يتناول حقيقة أن ذلك لا يمنع من enums من أنواع مختلفة (أو 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 تشارك بالفعل في تحويل شركات أعلن في أماكن أخرى ؛ أود أن تلقي بكل سرور التعليق حول هذا).

الجمع بين هذا مع ما سبق typesafe المصطلح يعطي شيء قريب نسبيا C++11 enum class في humanibility (القراءة و الصيانة) دون الحاجة إلى القيام بأي شيء غامض جدا.وأنا يجب أن أعترف أنه كان متعة القيام به ، لم أفكر قط أن في الواقع نسأل المترجم إذا كنت تتعامل مع enums أو لا...

أعتقد جافا enum سيكون نموذج يحتذى.أساسا, جافا شكل تبدو مثل هذا:

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

    private final String name;

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

    public String getName() {
        return name;
    }
}

ما هو المثير للاهتمام حول جافا النهج هو أن OK و CANCEL غير قابلة للتغيير ، المفرد حالات Result (مع الأساليب التي تراها).لا يمكنك إنشاء أي مزيد من حوادث Result.منذ انهم ووحدانية ، يمكنك مقارنة من خلال مؤشر/المراجع---مفيد جدا.:-)

ETA:في جافا, بدلا من القيام bitmasks باليد, بدلا من ذلك يمكنك استخدام EnumSet إلى تحديد مجموعة بت (تطبق Set واجهة يعمل مثل مجموعات---ولكن تنفيذها باستخدام bitmasks).أكثر قابلية للقراءة من الخطية بت التلاعب!

أعطى جوابا على هذا هنا, حول موضوع مختلف.إنه نمط مختلف من النهج الذي يسمح أكثر من نفس الوظائف دون الحاجة إلى تعديل الأصلي التعداد تعريف (وبالتالي مما يتيح الاستخدام في الحالات حيث لا تحديد التعداد).كما يسمح وقت التشغيل نطاق التدقيق.

الجانب السلبي من وجهة نظري هو أنه لا برمجيا فرض اقتران بين enum و مساعد الدرجة, لذا يجب تحديث في نفس الوقت.يعمل بالنسبة لي, ولكن YMMV.

أنا حاليا أكتب بلدي typesafe التعداد في المكتبة https://bitbucket.org/chopsii/typesafe-enums

أنا لست الأكثر خبرة C++ المطور من أي وقت مضى ولكن أنا أكتب هذا بسبب القصور في تعزيز قبو 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>, ، على سبيل المثال ، تمثل القيمة التي يمكن أ ، ب ، عدد صحيح أو لا شيء تقريبا Haskell98 مستويات من نوع الأمان.

سلبيات أن تكون على علم:

  • على الأقل مع زيادة -- انا على نظام مع زيادة 1.33 -- تقتصر على 20 بندا في البديل الخاص بك;هناك عمل حول ذلك
  • يؤثر على وقت الترجمة
  • مجنون رسائل خطأ-ولكن هذا هو C++ لك

التحديث

هنا لراحتك هو typesafe-enum "المكتبة".لصق هذا الرأس:

#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 ماكرو الذي يدمر بعض السحر.يا جيدا.لاحظ أيضا أن هناك الآن toStr وظيفة toInt وظيفة لتلبية العمليات متطلبات بسيطة تحويل سلاسل رجات.شرط لا أستطيع معرفة طريقة تكرار أكثر من العناصر.اسمحوا لي أن أعرف إذا كنت تعرف كيفية كتابة شيء من هذا القبيل.

لست متأكدا إذا كان هذا بعد فوات الأوان ، ولكن هناك مقال على GameDev.net الذي يرضي جميع ولكن النقطة 5 (القدرة على تكرار عبر التعداد):http://www.gamedev.net/reference/snippets/features/cppstringizing/

الطريقة الموصوفة من قبل المادة يسمح تحويل سلسلة دعم القائمة والتعدادات دون تغيير التعليمات البرمجية الخاصة بهم.إذا كنت تريد فقط دعم جديدة والتعدادات على الرغم من, كنت اذهب مع زيادة.التعداد (المذكورة أعلاه).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top