नाम प्रकार के लिए स्थान - सर्वोत्तम प्रथाएं


102

अक्सर, एक साथ कई अलग-अलग प्रकारों की आवश्यकता होती है। कभी-कभी, एक नाम क्लैश होता है। इसके दो समाधान दिमाग में आते हैं: एक नेमस्पेस का उपयोग करें, या 'बड़े' एनम तत्व नामों का उपयोग करें। फिर भी, नामस्थान समाधान के दो संभावित कार्यान्वयन हैं: नेस्टेड एनम या पूर्ण विकसित नामस्थान के साथ एक डमी क्लास।

मैं तीनों दृष्टिकोणों के पेशेवरों और विपक्षों की तलाश कर रहा हूं।

उदाहरण:

// 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
सबसे पहले, मैं कलर :: रेड, फीलिंग: एंग्री, इत्यादि का उपयोग
करूंगा

अच्छा सवाल है, मैंने नाम स्थान विधि का इस्तेमाल किया ....;)
मिनीस्कैलॉप

18
हर चीज पर "सी" उपसर्ग पठनीयता को नुकसान पहुंचाता है।
यूजर

8
@ उपयोगकर्ता: क्यों, हंगेरियन चर्चा खोलने के लिए धन्यवाद :)
xtofl

5
ध्यान दें कि आपको एनम का नाम रखने की आवश्यकता नहीं है enum e {...}, एनम गुमनाम हो सकते हैं, अर्थात enum {...}, जो नाम या वर्ग में लिपटे होने पर बहुत अधिक समझ में आता है।
krryk

जवाबों:


73

मूल C ++ 03 उत्तर:

Anamespace (over a class) से लाभ यह है कि आप usingजब चाहें घोषणाओं का उपयोग कर सकते हैं।

एक का उपयोग करने के साथ समस्या यह namespaceहै कि नामस्थान कोड में कहीं और विस्तारित किया जा सकता है। एक बड़े प्रोजेक्ट में, आपको इस बात की गारंटी नहीं होगी कि दो अलग-अलग एनमों को नहीं लगता कि दोनों को बुलाया जाता हैeFeelings

सरल-दिखने वाले कोड के लिए, मैं एक का उपयोग करता हूं struct, जैसा कि आप संभवतः चाहते हैं कि सामग्री सार्वजनिक हो।

यदि आप इनमें से कोई भी प्रैक्टिस कर रहे हैं, तो आप कर्व से आगे हैं और संभवत: इसे आगे बढ़ाने की आवश्यकता नहीं है।

नई, सी ++ 11 सलाह:

यदि आप C ++ 11 या उसके बाद का उपयोग कर रहे हैं, enum classतो enum के नाम के भीतर enum मानों को स्पष्ट रूप से स्कोप किया जाएगा।

आपके साथ enum classपूर्णांक प्रकारों में निहित रूपांतरण और तुलना खो देंगे, लेकिन व्यवहार में जो आपको अस्पष्ट या छोटी गाड़ी कोड खोजने में मदद कर सकते हैं।


4
मैं संरचना-विचार से सहमत हूं। और तारीफ के लिए धन्यवाद :)
xtofl

3
+1 मैं C ++ 11 "एनम क्लास" सिंटैक्स याद नहीं कर सका। उस सुविधा के बिना, एनम अधूरे हैं।
अंगूर

क्या उनका विकल्प "एनम वर्ग" के निहित दायरे के लिए "उपयोग" करना है। जैसे 'रंग का उपयोग कर जोड़ना' :: ई; कोड को 'cRed' के उपयोग की अनुमति दें और पता करें कि यह रंग :: e :: cRed होना चाहिए?
JVApen

20

FYI करें C ++ 0x में आपके द्वारा उल्लिखित मामलों के लिए एक नया वाक्यविन्यास है (देखें C ++ 0x विकी पेज )

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

11

मैंने पूर्ववर्ती उत्तरों को कुछ इस तरह से हाइब्रिड किया है: (EDIT: यह केवल प्री- C ++ 11 के लिए उपयोगी है। यदि आप C ++ 11 का उपयोग कर रहे हैं, तो उपयोग करें enum class)

मुझे एक बड़ी हेडर फ़ाइल मिली है जिसमें मेरे सभी प्रोजेक्ट एनम हैं, क्योंकि ये एनम वर्कर वर्ग के बीच साझा की जाती हैं और इन एंम को वर्कर क्लासेस में डालने का कोई मतलब नहीं है।

structवाक्यात्मक चीनी, और: सार्वजनिक बचा जाता है typedefकि आप वास्तव में अन्य कार्यकर्ता वर्गों के भीतर इन enums के चर घोषित करने देता है।

मुझे नहीं लगता कि किसी नामस्थान का उपयोग करने से मदद मिलती है। हो सकता है कि इस वजह से मैं एक सी # प्रोग्रामर हूँ, और तुम वहाँ है जब मूल्यों की बात कर enum प्रकार का नाम, उपयोग करने के लिए तो मैं यह करने के लिए इस्तेमाल कर रहा हूँ।

    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

मैं निश्चित रूप से इसके लिए एक वर्ग का उपयोग करने से बचूंगा; इसके बजाय एक नाम स्थान का उपयोग करें। यह सवाल उबलता है कि क्या किसी नाम स्थान का उपयोग करना है या एनम मूल्यों के लिए अद्वितीय आईडी का उपयोग करना है। व्यक्तिगत रूप से, मैं एक नाम स्थान का उपयोग करूंगा ताकि मेरी आईडी छोटी और उम्मीद से अधिक आत्म-व्याख्यात्मक हो सके। तब एप्लिकेशन कोड एक 'नेमस्पेस का उपयोग करके' निर्देश का उपयोग कर सकता है और सब कुछ अधिक पठनीय बना सकता है।

ऊपर अपने उदाहरण से:

using namespace Colors;

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

क्या आप इस बात का संकेत दे सकते हैं कि आप एक वर्ग पर एक नाम स्थान क्यों पसंद करेंगे?
xtofl

@xtofl: आप of क्लास कलर्स का उपयोग करके 'नहीं लिख सकते हैं
MSalters

2
@MSalters: आप भी नहीं लिख सकते Colors someColor = Red;, क्योंकि नाम स्थान एक प्रकार का नहीं है। आपको Colors::e someColor = Red;इसके बजाय लिखना होगा, जो काफी प्रति-सहज है।
SasQ

अगर आप एक बयान में इसका इस्तेमाल करना चाहते हैं तो क्या @SasQ का Colors::e someColorभी उपयोग नहीं करना होगा ? यदि आप एक अनाम का उपयोग करते हैं तो स्विच एक का मूल्यांकन करने में सक्षम नहीं होगा । struct/classswitchenumstruct
मैकबेथ की पहेली

1
क्षमा करें, लेकिन const e cशायद ही मेरे लिए पठनीय लगता है :-) ऐसा मत करो। एक नाम स्थान का उपयोग करना हालांकि, ठीक है।
धामन

7

एक वर्ग या एक नाम स्थान का उपयोग करने के बीच एक अंतर यह है कि वर्ग को नामस्थान की तरह फिर से खोला नहीं जा सकता है। यह इस संभावना से बचता है कि भविष्य में नाम स्थान का दुरुपयोग हो सकता है, लेकिन समस्या यह भी है कि आप गणना के सेट में जोड़ नहीं सकते हैं।

एक वर्ग का उपयोग करने के लिए एक संभावित लाभ यह है कि उनका उपयोग टेम्पलेट प्रकार के तर्क के रूप में किया जा सकता है, जो नामस्थान के लिए मामला नहीं है:

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

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

व्यक्तिगत रूप से, मैं उपयोग करने का प्रशंसक नहीं हूं , और मैं पूरी तरह से योग्य नामों को पसंद करता हूं, इसलिए मैं वास्तव में नामस्थान के लिए प्लस के रूप में नहीं देखता हूं। हालाँकि, यह संभवतः सबसे महत्वपूर्ण निर्णय नहीं है जो आप अपनी परियोजना में करेंगे!


वर्गों को फिर से खोलना भी एक संभावित नुकसान नहीं है। रंगों की सूची भी परिमित नहीं है।
MSalters

1
मुझे लगता है कि किसी वर्ग को रिपीट करना एक संभावित लाभ नहीं है। अगर मुझे और रंग चाहिए, तो मैं और अधिक रंगों के साथ कक्षा को फिर से जोड़ दूंगा। अगर मैं ऐसा नहीं कर सकता (कहूं तो मेरे पास कोड नहीं है), तो मैं इसे किसी भी हाल में छूना नहीं चाहता।
थॉमस एडिंग

@MSalters: किसी वर्ग को फिर से खोलने की कोई संभावना न केवल एक नुकसान है, बल्कि एक सुरक्षा उपकरण भी है। क्योंकि जब किसी वर्ग को फिर से खोलना और एनम में कुछ मान जोड़ना संभव होगा, तो यह अन्य लाइब्रेरी कोड को तोड़ सकता है जो पहले से ही उस एनम पर निर्भर करता है और केवल मूल्यों के पुराने सेट को जानता है। यह तब खुशी से उन नए मूल्यों को स्वीकार करता है, लेकिन उनके साथ क्या करना है, यह नहीं जानने के लिए रनटाइम पर ब्रेक। ओपन-क्लोज्ड सिद्धांत याद रखें: क्लास को संशोधित करने के लिए बंद किया जाना चाहिए , लेकिन विस्तार के लिए खुला होना चाहिए । विस्तार से मेरा मतलब मौजूदा कोड से नहीं जोड़ना है, लेकिन नए कोड (जैसे व्युत्पन्न) के साथ इसे लपेटना है।
SasQ

इसलिए जब आप एनम का विस्तार करना चाहते हैं, तो आपको इसे पहले से प्राप्त एक नया प्रकार बनाना चाहिए (यदि केवल सी ++ में आसानी से संभव हो ...; /)। फिर इसे नए कोड द्वारा सुरक्षित रूप से उपयोग किया जा सकता है जो उन नए मूल्यों को समझता है, लेकिन पुराने कोड द्वारा केवल पुराने मूल्यों को स्वीकार किया जाएगा (उन्हें परिवर्तित करके)। उन्हें गलत प्रकार (विस्तारित एक) के रूप में उन नए मूल्यों में से किसी एक को स्वीकार नहीं करना चाहिए। केवल पुराने मान जो वे समझते हैं, उन्हें सही (आधार) प्रकार के होने के रूप में स्वीकार किया जाता है (और गलती से नए प्रकार के भी होते हैं, इसलिए इसे नए कोड के रूप में भी स्वीकार किया जा सकता है)।
SasQ

7

एक वर्ग का उपयोग करने का लाभ यह है कि आप इसके शीर्ष पर एक पूर्ण श्रेणी का निर्माण कर सकते हैं।

#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;
    }
}

जैसा कि ऊपर दिए गए उदाहरण से पता चलता है, एक वर्ग का उपयोग करके आप कर सकते हैं:

  1. निषेध (दुख की बात है, संकलन-समय नहीं) C ++ अमान्य मान से कलाकारों की अनुमति देने से,
  2. नए बनाए गए एनमों के लिए एक (गैर-शून्य) डिफ़ॉल्ट सेट करें,
  3. आगे की विधियाँ जोड़ें, जैसे चुनाव का स्ट्रिंग प्रतिनिधित्व वापस करने के लिए।

बस ध्यान दें कि आपको घोषित करने की आवश्यकता है operator enum_type()ताकि C ++ को पता चले कि अपनी कक्षा को अंतर्निहित एनम में कैसे परिवर्तित किया जाए। अन्यथा, आप एक switchस्टेटमेंट में टाइप पास नहीं कर पाएंगे ।


क्या यह समाधान किसी तरह से संबंधित है जो यहां दिखाया गया है ?: en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum मैं सोच रहा हूं कि इसे कैसे बनाया जाए कि मुझे इस पैटर्न को हर बार फिर से लिखना नहीं पड़ता। मुझे इसका उपयोग करने की आवश्यकता है।
सासक्यू

@ सासक्यू: ऐसा ही लगता है, हाँ। शायद यही विचार है। हालाँकि, मुझे यकीन नहीं है कि अगर कोई टेम्पलेट फायदेमंद है, जब तक कि आप वहां बहुत सारे 'सामान्य' तरीके नहीं जोड़ते।
गोरी

1. वास्तव में सच नहीं है। आप संकलित समय की जांच कर सकते हैं कि एनम वैध है, const_expr के माध्यम से, या एक इंट के लिए निजी कंस्ट्रक्टर के माध्यम से।
xryl669

5

चूँकि एनम उनके घेरने के दायरे में हैं, इसलिए संभव है कि ग्लोबल नेमस्पेस को प्रदूषित करने से बचाने के लिए और नाम टकराव से बचने में मदद करने के लिए उन्हें किसी चीज़ में लपेट दिया जाए । मैं कक्षा में नाम रखने की जगह को सिर्फ इसलिए पसंद करता हूं क्योंकि namespaceहोल्डिंग का एक बैग classजैसा लगता है , जबकि एक मजबूत वस्तु की तरह महसूस होता है (सीएफ structबनाम बनाम classबहस)। नाम स्थान के लिए एक संभावित लाभ यह है कि इसे बाद में बढ़ाया जा सकता है - यदि आप तृतीय-पक्ष कोड के साथ काम कर रहे हैं जिसे आप संशोधित नहीं कर सकते हैं।

जब हम C ++ 0x के साथ एनम क्लासेस प्राप्त करते हैं, तो यह निश्चित रूप से सभी प्रकार की लूट है।


enum classes ... यह देखने की जरूरत है!
xtofl

3

मैं भी कक्षाओं में अपनी दुश्मनी लपेटता हूं।

जैसा कि रिचर्ड कॉर्डन द्वारा संकेत दिया गया है, एक वर्ग का लाभ यह है कि यह सी ++ अर्थ में एक प्रकार है और इसलिए आप इसे टेम्पलेट के साथ उपयोग कर सकते हैं।

मेरे पास विशेष टूलबॉक्स है: मेरी ज़रूरतों के लिए Enum क्लास जो कि मैं उन सभी टेम्प्लेट्स के लिए विशेषज्ञ हूँ जो बुनियादी फ़ंक्शंस प्रदान करता है (मुख्यतः: एक एनुम मान को std :: string में मैप करना ताकि I / O पढ़ने में आसान हो)।

मेरे छोटे टेम्पलेट में अनुमत मानों के लिए वास्तव में जाँच का अतिरिक्त लाभ है। संकलक यह जाँचने पर शिथिल है कि क्या मान वास्तव में एनम में है:

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

इसने मुझे हमेशा परेशान किया कि कंपाइलर इसे पकड़ नहीं पाएगा, क्योंकि आप एक ऐसे एनम वैल्यू से बचे हैं जिसका कोई मतलब नहीं है (और जिसकी आपको उम्मीद नहीं होगी)।

इसी तरह:

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

एक बार फिर से मुख्य एक त्रुटि वापस आ जाएगी।

समस्या यह है कि संकलक सबसे छोटे प्रतिनिधित्व में फिट होगा (यहां हमें 2 बिट्स की आवश्यकता है) और यह कि इस प्रतिनिधित्व में फिट होने वाली हर चीज को एक वैध मूल्य माना जाता है।

समस्या यह भी है कि कभी-कभी आपके पास स्विच के बजाय संभावित मानों पर एक लूप होगा ताकि आपको हर बार स्विच करने की ज़रूरत न पड़े, लेकिन जब भी आप एनम के लिए मूल्य जोड़ते हैं, तो आपको स्विच करना पड़ता है।

मेरे सभी छोटे सहायक में सभी वास्तव में मेरे एनम के लिए चीजों को कम करते हैं (निश्चित रूप से, यह कुछ उपरि जोड़ता है) और यह केवल इसलिए संभव है क्योंकि मैं प्रत्येक एनम को अपनी संरचना में घोंसला बनाता हूं :)


4
दिलचस्प। क्या आपको अपने Enum वर्ग की परिभाषा साझा करने का मन है?
मोमेरा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.