c - टाइप सेफ एनम कैसे बनाएं?




enums type-safety (3)

अंततः, आप जो चाहते हैं, वह एक अवैध गणना मूल्य का उपयोग करते समय एक चेतावनी या त्रुटि है।

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

सी में एनम के साथ टाइप सुरक्षा प्राप्त करने के लिए समस्याग्रस्त है, क्योंकि वे अनिवार्य रूप से सिर्फ पूर्णांक हैं। और एन्यूमरेशन कांस्टेंट वास्तव में मानक द्वारा टाइप int रूप में परिभाषित किए गए हैं।

कुछ प्रकार की सुरक्षा प्राप्त करने के लिए मैं इस तरह पॉइंटर्स के साथ ट्रिक्स करता हूं:

typedef enum
{
  BLUE,
  RED
} color_t;

void color_assign (color_t* var, color_t val) 
{ 
  *var = val; 
}

क्योंकि पॉइंटर्स में मानों की तुलना में कठोर नियम होते हैं, इसलिए यह इस तरह कोड को रोकता है:

int x; 
color_assign(&x, BLUE); // compiler error

लेकिन यह इस तरह कोड को नहीं रोकता है:

color_t color;
color_assign(&color, 123); // garbage value

ऐसा इसलिए है क्योंकि गणना स्थिरांक अनिवार्य रूप से सिर्फ एक int और एक संयुग्मन चर को स्पष्ट रूप से सौंपा जा सकता है।

क्या इस तरह के फ़ंक्शन या मैक्रो color_assign लिखने का एक तरीका है, जो एन्यूमरेशन स्थिरांक के लिए भी पूर्ण प्रकार की सुरक्षा प्राप्त कर सकता है?


एक struct साथ एक प्रकार की सुरक्षा को लागू कर सकता है:

struct color { enum { THE_COLOR_BLUE, THE_COLOR_RED } value; };
const struct color BLUE = { THE_COLOR_BLUE };
const struct color RED  = { THE_COLOR_RED  };

चूंकि color सिर्फ एक लिपटे हुए पूर्णांक है, इसे मान द्वारा या सूचक के रूप में पारित किया जा सकता है क्योंकि कोई int साथ क्या करेगा। color की इस परिभाषा के साथ, color_assign(&val, 3); इसके साथ संकलन करने में विफल:

त्रुटि: 'color_assign' के तर्क 2 के लिए असंगत प्रकार

     color_assign(&val, 3);
                        ^

पूर्ण (कामकाजी) उदाहरण:

struct color { enum { THE_COLOR_BLUE, THE_COLOR_RED } value; };
const struct color BLUE = { THE_COLOR_BLUE };
const struct color RED  = { THE_COLOR_RED  };

void color_assign (struct color* var, struct color val) 
{ 
  var->value = val.value; 
}

const char* color_name(struct color val)
{
  switch (val.value)
  {
    case THE_COLOR_BLUE: return "BLUE";
    case THE_COLOR_RED:  return "RED";
    default:             return "?";
  }
}

int main(void)
{
  struct color val;
  color_assign(&val, BLUE);
  printf("color name: %s\n", color_name(val)); // prints "BLUE"
}

ऑनलाइन (डेमो) के साथ खेलते हैं


शीर्ष उत्तर काफ़ी अच्छा है, लेकिन इसमें डाउनसाइड है कि इसे संकलित करने के लिए बहुत सारे C99 और C11 फ़ीचर की आवश्यकता है, और इसके शीर्ष पर, यह असाइनमेंट को बहुत ही अप्राकृतिक बनाता है: आपको एक मैजिक color_assign() फ़ंक्शन का उपयोग करना होगा या मानक = ऑपरेटर के बजाय चारों ओर डेटा स्थानांतरित करने के लिए मैक्रो।

(स्पष्ट रूप से, यह सवाल स्पष्ट रूप से पूछा गया था कि कैसे color_assign() लिखना है color_assign() , लेकिन यदि आप प्रश्न को अधिक विस्तृत रूप से देखते हैं, तो यह वास्तव में है कि आपके कोड को परिवर्तित करने के लिए कैसे किसी प्रकार के एन्यूमरेटेड कॉन्स्टेंट के साथ टाइप किया जाए, और मैं विचार करूंगा उत्तर के लिए निष्पक्ष खेल होने के लिए टाइप-सेफ्टी पाने के लिए पहले स्थान पर color_assign() आवश्यकता नहीं है।)

पॉइंटर्स उन कुछ आकारों में से हैं जिन्हें C टाइप-सेफ मानते हैं, इसलिए वे इस समस्या को हल करने के लिए एक स्वाभाविक उम्मीदवार बनाते हैं। तो मैं इसे इस तरह से हमला करूँगा: बल्कि एक enum का उपयोग करने के बजाय, मैं अद्वितीय, पूर्वानुमेय सूचक मान रखने में सक्षम होने के लिए थोड़ी याददाश्त का त्याग करूंगा, और फिर अपने "एनम" का निर्माण करने के लिए कुछ वास्तव में होके फंकी #define स्टेटमेंट का उपयोग करूंगा ( हाँ, मुझे पता है कि मैक्रोज़ मैक्रो नामस्थान को प्रदूषित करते हैं, लेकिन एनम संकलक के ग्लोबल नेमस्पेस को प्रदूषित करते हैं, इसलिए मैं इसे एक समान व्यापार के करीब मानता हूं):

color.h :

typedef struct color_struct_t *color_t;

struct color_struct_t { char dummy; };

extern struct color_struct_t color_dummy_array[];

#define UNIQUE_COLOR(value) \
    (&color_dummy_array[value])

#define RED    UNIQUE_COLOR(0)
#define GREEN  UNIQUE_COLOR(1)
#define BLUE   UNIQUE_COLOR(2)

enum { MAX_COLOR_VALUE = 2 };

बेशक, यह आवश्यक है कि आपके पास बस पर्याप्त मेमोरी है जो कहीं न कहीं यह सुनिश्चित करने के लिए आरक्षित है कि उन सूचक मूल्यों पर कभी भी कुछ और नहीं लिया जा सकता है:

color.c :

#include "color.h"

/* This never actually gets used, but we need to declare enough space in the
 * BSS so that the pointer values can be unique and not accidentally reused
 * by anything else. */
struct color_struct_t color_dummy_array[MAX_COLOR_VALUE + 1];

लेकिन उपभोक्ता के दृष्टिकोण से, यह सब छिपा हुआ है: color_t बहुत ही अपारदर्शी वस्तु है। आप इसे मान्य color_t मान और NULL के अलावा कुछ भी असाइन नहीं कर सकते:

user.c :

#include <stddef.h>
#include "color.h"

void foo(void)
{
    color_t color = RED;    /* OK */
    color_t color = GREEN;  /* OK */
    color_t color = NULL;   /* OK */
    color_t color = 27;     /* Error/warning */
}

यह ज्यादातर मामलों में अच्छा काम करता है, लेकिन इसमें switch स्टेटमेंट में काम नहीं करने की समस्या है; आप एक पॉइंटर (जो एक शर्म की बात है) पर switch नहीं कर सकते। लेकिन अगर आप स्विचिंग को संभव बनाने के लिए एक और मैक्रो जोड़ने के लिए तैयार हैं, तो आप कुछ ऐसा कर सकते हैं जो "अच्छा पर्याप्त" हो।

color.h :

...

#define COLOR_NUMBER(c) \
    ((c) - color_dummy_array)

user.c :

...

void bar(color_t c)
{
    switch (COLOR_NUMBER(c)) {
        case COLOR_NUMBER(RED):
            break;
        case COLOR_NUMBER(GREEN):
            break;
        case COLOR_NUMBER(BLUE):
            break;
    }
}

क्या यह एक अच्छा उपाय है? मैं इसे महान नहीं कहूंगा, क्योंकि यह दोनों कुछ मेमोरी बर्बाद करता है और मैक्रो नेमस्पेस को प्रदूषित करता है, और यह आपको अपने रंग मूल्यों को स्वचालित रूप से असाइन करने के लिए enum का उपयोग नहीं करने देता है, लेकिन यह समस्या को हल करने का एक और तरीका है जिसके परिणामस्वरूप कुछ और अधिक होता है प्राकृतिक उपयोग, और शीर्ष उत्तर के विपरीत, यह C89 पर वापस जाने के लिए सभी तरह से काम करता है।







type-safety