سؤال

في C++ مشروع أعمل عليه ، لدي العلم نوع من القيمة التي يمكن أن يكون أربع القيم.هؤلاء الأربعة الأعلام يمكن أن تكون جنبا إلى جنب.أعلام تصف السجلات في قاعدة البيانات و يمكن أن تكون:

  • رقم قياسي جديد
  • حذف سجل
  • تعديل سجل
  • سجل موجود

الآن لكل سجل أتمنى أن تبقى هذه السمة ، حتى أتمكن من استخدام التعداد:

enum { xNew, xDeleted, xModified, xExisting }

ولكن في أماكن أخرى في التعليمات البرمجية, أنا بحاجة إلى تحديد السجلات التي تكون مرئية للمستخدم ، لذا أود أن تكون قادرة على تمرير هذا واحد المعلمة, مثل:

showRecords(xNew | xDeleted);

يبدو أن لدي ثلاثة ممكن appoaches:

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

أو

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

أو

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

متطلبات مساحة هامة (بايت مقابل الباحث) ولكن ليس حاسما.مع تعريف خسرت نوع السلامة ، enum خسرت بعض المساحة (الأعداد الصحيحة) و من المحتمل أن يلقي عندما أريد أن أفعل أحادي المعامل العملية.مع const أعتقد أيضا تفقد النوع السلامة وبما عشوائي uint8 يمكن أن تحصل عن طريق الخطأ.

هناك بعض نظافة الطريق ؟

إذا كان لا ماذا كنت تستخدم ولماذا ؟

P. S.بقية الكود بدلا نظيفة الحديث C++ دون #defineو لقد استخدمت مساحات والقوالب في بعض الأماكن ، حتى تلك التي ليست من السؤال أيضا.

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

المحلول

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

مكان التعداد في مساحة لمنع الثوابت من تلويث مساحة العالمي.

namespace RecordType {

وهو التعداد يعلن ويحدد وقت الترجمة فحص كتبته.دائما استخدام وقت الترجمة نوع التحقق للتأكد من الحجج و المتغيرات تعطى من النوع الصحيح.ليست هناك حاجة ل typedef في C++.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

خلق آخر عضو غير صالح الدولة.هذا يمكن أن تكون مفيدة كما رمز الخطأ;على سبيل المثال عندما تريد العودة الدولة ولكن I/O تفشل العملية.وهو مفيد أيضا في التصحيح ؛ استخدامه في initialisation قوائم destructors لمعرفة إذا كان المتغير قيمة ينبغي أن تستخدم.

xInvalid = 16 };

نعتبر أن لديك اثنين من أغراض هذا النوع.لتتبع الوضع الحالي سجل وخلق قناع لتحديد السجلات في بعض الدول.إنشاء وظيفة مضمنة لاختبار إذا كانت قيمة نوع صالحة للغرض الخاص بك ؛ كدولة علامة مقابل حالة القناع.هذا سوف التقاط الحشرات كما typedef هو مجرد int و قيمة مثل 0xDEADBEEF قد يكون في متغير من خلال uninitialised أو mispointed المتغيرات.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

إضافة using التوجيه إذا كنت ترغب في استخدام النوع في كثير من الأحيان.

using RecordType ::TRecordType ;

قيمة فحص وظائف مفيدة يؤكد في فخ سوء القيم حالما يتم استخدامها.وأسرع يمكنك التقاط خطأ عند تشغيل أقل من الضرر الذي يمكن القيام به.

وهنا بعض الأمثلة على وضع كل ذلك معا.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

الطريقة الوحيدة لضمان القيمة الصحيحة سلامة استخدام مخصصة الدرجة مع المشغل الزائدة و التي ترك ممارسة قارئ آخر.

نصائح أخرى

ننسى يعرف

أنها سوف تلوث التعليمات البرمجية الخاصة بك.

bitfields?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

لا تستخدم أي وقت مضى أن.كنت أكثر قلقا مع سرعة من ترشيد 4 رجات.باستخدام بعض المجالات هو في الواقع أبطأ من الوصول إلى أي نوع آخر.

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

المصدر: http://en.wikipedia.org/wiki/Bit_field:

وإذا كنت بحاجة إلى المزيد من الأسباب لا استخدام bitfields ، وربما ريمون تشن سوف اقناع لكم في الشيء الجديد القديم وظيفة: تحليل التكاليف والمنافع من bitfields على مجموعة من القيم المنطقية في http://blogs.msdn.com/oldnewthing/archive/2008/11/26/9143050.aspx

const int ؟

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

ووضعها في مساحة الاسم هو بارد.إذا كانت أعلنت في CPP أو ملف الرأس ، سوف تكون القيم المضمنة.عليك أن تكون قادرا على استخدام رمز التبديل على تلك القيم ، ولكن فإنه سيتم زيادة طفيفة اقتران.

آه, نعم: إزالة ثابت الكلمة.ثابت هو مستنكر في C++ عند استخدامها كما كنت تفعل ، إذا uint8 هو أعلى نوع, أنت لن تحتاج أن تعلن هذا في رأس المدرجة من قبل مصادر متعددة من نفس الوحدة.في النهاية يجب أن يكون الكود:

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

مشكلة هذا النهج هو أن رمز يعرف قيمة الثوابت التي يزيد قليلا اقتران.

التعداد

نفس const int, مع أقوى بعض الشيء في الكتابة.

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

أنها لا تزال تلويث مساحة العالمي ، على الرغم من.بالمناسبة... إزالة الرموز المميزة ل typedef.كنت تعمل في C++.تلك typedefs من enums و البنيات يتم تلويث رمز أكثر من أي شيء آخر.

والنتيجة هي نوعا ما:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

كما ترون ، enum هو تلويث مساحة العالمي.إذا وضعت هذا التعداد في مساحة, سوف يكون شيئا مثل:

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int ؟

إذا كنت تريد أن تقلل اقتران (أيكونها قادرة على إخفاء قيم الثوابت ، لذا تعديلها على النحو المطلوب دون الحاجة كامل recompilation) ، يمكنك أن يعلن رجات كما خارجي في الرأس ، وكما هو ثابت في الملف CPP, كما في المثال التالي:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

و:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

أنك لن تكون قادرا على استخدام رمز التبديل على تلك الثوابت ، على الرغم من.لذا في النهاية اختيار السموم...:-p

هل استبعدت std::bitset?مجموعات من الأعلام ما يفعل.هل

typedef std::bitset<4> RecordType;

ثم

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

لأن هناك مجموعة من المشغل الزائدة عن bitset ، يمكنك القيام به الآن

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

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

على افتراض لديك استبعد bitset أنا التصويت التعداد.

أنا لا أصدق أن صب enums هو عيب خطير وهو موافق لذلك فمن قليلا صاخبة ، وتعيين خارج نطاق القيمة إلى التعداد غير معرف سلوك لذلك فمن الممكن نظريا أن تطلق النار على قدمك على بعض غير عادية C++ تطبيقات.ولكن إذا كنت تفعل ذلك فقط عند الضرورة (وهو عند الذهاب من الباحث أن التعداد iirc) ، انه طبيعي رمز أن الناس قد رأيت من قبل.

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

وبالتالي فإن التعداد لا يوجد لديه عيوب مقارنة مع الآخرين ، كما ميزة يمنحك قليلا من نوع-السلامة (لا يمكنك تعيين عشوائي قيمة عدد صحيح بدون صراحة الصب) وتنظيف الطرق من الإشارة إلى كل شيء.

عن تفضيل أود أيضا وضع "= 2" في التعداد بالمناسبة.انها ليست ضرورية ، ولكن "المبدأ" يشير إلى أن كل 4 التعاريف يجب أن ننظر نفسه.

وهنا بعض المقالات على const مقابلوحدات الماكرو مقابلenums:

رمزية الثوابت
التعداد الثوابت مقابلثابت الكائنات

أعتقد يجب تجنب وحدات الماكرو خصوصا كتبت أكثر من القانون الجديد الخاص بك هو في الحديث C++.

إذا كان ذلك ممكنا لا تستخدم وحدات الماكرو.أنها ليست الكثير من الإعجاب عندما يتعلق الأمر الحديثة C++.

Enums سيكون أكثر ملاءمة لأنها توفر "معنى معرفات" وكذلك نوع الأمان.يمكن أن أقول لكم بوضوح "xDeleted" هو من "RecordType" و التي تمثل "نوع سجل" (واو!) حتى بعد سنوات.Consts يتطلب تعليق ل ، كما أنها تتطلب الذهاب صعودا وهبوطا في التعليمات البرمجية.

مع تعريف خسرت نوع السلامة

ليس بالضرورة...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

و مع التعداد أفقد بعض المساحة (الأعداد الصحيحة)

ليس بالضرورة - ولكن يجب عليك أن تكون صريحا في نقاط التخزين...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

و من المحتمل أن يلقي عندما أريد أن أفعل أحادي المعامل العملية.

يمكنك إنشاء شركات أخذ الألم من ذلك:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

مع const أعتقد تفقد أيضا نوع السلامة وبما عشوائي uint8 يمكن أن تحصل عن طريق الخطأ.

يمكن أن يحدث نفس الشيء مع أي من هذه الآليات:مجموعة قيمة الشيكات عادة متعامد نوع السلامة (على الرغم من المستخدم-تعريف-أنواع - أيالفئات الخاصة بك - يمكن فرض "الثوابت" عن بياناتها).مع enums, المترجم هو حر في اختيار نوع أكبر لاستضافة القيم ، uninitialised تالفة أو مجرد ملكة جمال ضبط التعداد متغير يمكن أن لا تزال في نهاية المطاف interpretting بت نمط وعدد لن تتوقعها - مقارنة غير متكافئة إلى أي من التعداد معرفات ، أي مزيج منها ، 0.

هناك بعض نظافة الطريق ؟ / إذا لم يكن كذلك ، ماذا كنت تستخدم ولماذا ؟

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

هل يمكن القول أن هذا هو "أنظف":

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

أنا غير مبال:البيانات بت حزمة تشديد لكن رمز ينمو بشكل كبير...يعتمد على كيفية العديد من الأشياء عليك ، lamdbas - جميلة كما هي - لا تزال مسير و صعوبة في الحصول على حق من المعامل أملاح الإماهة الفموية.

BTW /- الجدل حول موضوع السلامة ضعيف جدا IMHO - أفضل تذكرت كخلفية الاعتبار بدلا من أن تصبح المهيمنة قرار قيادة القوة ؛ مشاركة مزامنة عبر bitfields هو أكثر احتمالا الممارسة حتى إذا علم من التعبئة (mutexes نسبيا ضخمة أعضاء البيانات - يجب أن يكون حقا تشعر بالقلق إزاء الأداء إلى النظر في وجود عدة mutexes على أعضاء جسم واحد ، أود أن ننظر بعناية كافية لإشعار كانوا قليلا من المجالات).أي كلمة الفرعية الحجم نوع يمكن أن يكون نفس المشكلة (مثلا ، a uint8_t).على أي حال, يمكنك أن تحاول الذرية مقارنة-و-تبادل أسلوب العمليات إذا كنت يائسة للحصول على أعلى التزامن.

حتى إذا كان لديك لاستخدام 4 بايت لتخزين enum (أنا لست على دراية مع C++ -- وأنا أعلم أنك يمكن أن تحدد الأساسية اكتب في C#), فإنه لا يزال يستحق ذلك-استخدام enums.

في هذا اليوم وهذا العصر من ملقمات مع GBs من الذاكرة أشياء مثل 4 بايت مقابل1 بايت من الذاكرة على مستوى التطبيق بشكل عام لا يهم.بالطبع, إذا كان في الوضع الخاص بك, استخدام الذاكرة مهم (لا يمكنك الحصول على C++ استخدام البايت إلى enum), ثم يمكنك أن تنظر في 'ثابت const' الطريق.

في نهاية اليوم, عليك أن تسأل نفسك, هل يستحق صيانة ضرب من استخدام 'ثابت const' 3 بايت من الذاكرة المدخرات الخاصة بك بنية البيانات ؟

شيء آخر أن نأخذ في الاعتبار -- IIRC, على x86, هياكل البيانات هي 4 بايت محاذاة لذلك لم يكن لديك عدد من البايت-عرض العناصر في 'سجل' هيكل قد لا يهم فعلا.الاختبار وتأكد من أنه لا قبل إجراء المفاضلة في الصيانة الأداء/الفضاء.

إذا كنت تريد نوع السلامة من الطبقات ، مع راحة من تعداد جملة و بت فحص النظر آمنة التسميات في C++.لقد عملت مع المؤلف و هو ذكي جدا.

حذار ، على الرغم من.في النهاية, هذا يستخدم حزمة قوالب و وحدات الماكرو!

هل فعلا تحتاج إلى تمرير جميع أنحاء العلم القيم المفاهيمي كله ، أو أنت ذاهب إلى الكثير من في العلم رمز ؟ وفي كلتا الحالتين, وأعتقد أن وجود هذه الفئة أو البنية من 1 بت bitfields قد يكون في الواقع أكثر وضوحا:

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

ثم سجل فئة يمكن أن يكون البنية RecordFlag الأعضاء متغير, وظائف يمكن أن تأخذ حجج من نوع struct RecordFlag ، إلخ.المترجم يجب أن حزمة bitfields معا ، وتوفير مساحة.

ربما لن استخدم التعداد لهذا النوع من الشيء حيث القيم التي يمكن أن تكون مجتمعة معا ، أكثر عادة enums هي حصرية الدول.

ولكن أيا كانت الطريقة التي تستخدمها لجعل الأمر أكثر وضوحا أن هذه هي القيم التي هي البتات التي يمكن أن تكون مجتمعة معا ، استخدام بناء الجملة الفعلية القيم بدلا من ذلك:

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

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

على أساس قبلة, التماسك العالية والمنخفضة اقتران, ، نسأل هذه الأسئلة -

  • من يريد أن يعرف ؟ صفي مكتبتي ، الطبقات الأخرى ، المكتبات الأخرى ، 3rd الأطراف
  • ما هو مستوى من التجريد هل أنا بحاجة إلى توفير ؟ هل المستهلك فهم العمليات قليلا.
  • سوف لا يكون لديك واجهة من VB/C# إلخ ؟

هناك كتاب عظيم "على نطاق واسع C++ تصميم البرمجيات"هذا يعزز قاعدة أنواع خارجيا ، إذا كان يمكنك تجنب آخر ملف الرأس/واجهة dependancy يجب أن تحاول.

إذا كنت تستخدم Qt يجب عليك إلقاء نظرة على QFlags.على QFlags الدرجة يوفر نوع وسيلة آمنة لتخزين أو مجموعات من التعداد القيم.

أنا أفضل الذهاب مع

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

ببساطة لأن:

  1. هو أنظف و يجعل رمز للقراءة للصيانة.
  2. ذلك منطقيا المجموعات الثوابت.
  3. مبرمج الوقت هو أكثر أهمية ، ما لم يكن عملك هو لإنقاذ أولئك 3 بايت.

لا أود أن أكثر من مهندس كل شيء لكن في بعض الأحيان في هذه الحالات قد يكون من المفيد إنشاء (الصغيرة) فئة لتغليف هذه المعلومات.إذا قمت بإنشاء فئة RecordType ثم قد يكون لها وظائف مثل:

الفراغ setDeleted();

الفراغ clearDeleted();

منطقي isDeleted();

الخ...(أو أي اتفاقية الدعاوى)

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

فئة قد تعطيك القدرة على إرفاق مغزى تسجيل معلومات عن كل دولة ، يمكن إضافة وظيفة إعادة تمثيل سلسلة من الوضع الحالي إلخ (أو استخدام تدفق المشغلين<<').

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

هل يمكن أن يكون الفعلية بت القيم غير مرئية لبقية 'العالم' إذا كانوا في مجهول الاسم داخل ملف cpp وليس في ملف الرأس.

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

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