C ऑब्जेक्ट को डिफ़ॉल्ट रूप से बनाने के लिए C ++ को उपयोगकर्ता-प्रदान किए गए डिफ़ॉल्ट निर्माता की आवश्यकता क्यों है?


99

C ++ मानक (धारा 8.5) कहता है:

यदि कोई प्रोग्राम किसी कॉन्स्टेबल-योग्य टाइप T के ऑब्जेक्ट के डिफॉल्ट इनिशियलाइज़ेशन के लिए कहता है, तो T एक क्लास-प्रकार होगा जो यूजर द्वारा प्रदान किया गया डिफॉल्ट कंस्ट्रक्टर है।

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

struct B{
  B():x(42){}
  int doSomeStuff() const{return x;}
  int x;
};

struct A{
  A(){}//other than "because the standard says so", why is this line required?

  B b;//not required for this example, just to illustrate
      //how this situation isn't totally useless
};

int main(){
  const A a;
}

2
आपके उदाहरण में लाइन की आवश्यकता प्रतीत नहीं होगी (देखें ideone.com/qqiXR ) क्योंकि आपने घोषित किया था लेकिन परिभाषित / प्रारंभ नहीं किया था a, लेकिन gcc-4.3.4 इसे तब भी स्वीकार करता है जब आप इसे करते हैं (देखें ideone.com/uHvFS )
रे तोल

उपरोक्त उदाहरण दोनों को घोषित और परिभाषित करता है a। कॉमेउ एक त्रुटि "कांस्टेबल वैरिएबल" पैदा करता है, इसके लिए एक इनिशियलाइज़र की आवश्यकता होती है - क्लास "ए" को कोई स्पष्ट रूप से डिफॉल्ट कंस्ट्रक्टर घोषित नहीं किया जाता है "अगर लाइन में टिप्पणी की जाती है।
कारू


4
यह C ++ 11 में तय किया गया है, आप const A a{}:)
हॉवर्ड लोवेट

जवाबों:


10

यह एक दोष माना जाता था (मानक के सभी संस्करणों के खिलाफ) और इसे कोर वर्किंग ग्रुप (सीडब्ल्यूजी) दोष 253 द्वारा हल किया गया था । मानक राज्यों के लिए नया शब्द http://eel.is/c++draft/dcl.init#7 में है

यदि टी का डिफ़ॉल्ट-प्रारंभिक उपयोग टी-के उपयोगकर्ता-प्रदान किए गए कंस्ट्रक्टर (बेस क्लास से विरासत में नहीं मिला है) या यदि हो तो एक क्लास टाइप टी कॉन्स्टेबल-डिफॉल्ट-कंस्ट्रक्टेबल है।

  • प्रत्येक प्रत्यक्ष गैर-वैरिएंट गैर-स्थैतिक डेटा सदस्य M का T एक डिफ़ॉल्ट सदस्य आरंभीकरणकर्ता है या, यदि M वर्ग प्रकार X (या इसके प्रकार) का है, तो X कांस्टेबल-डिफॉल्ट-कंस्ट्रक्टेबल है,
  • यदि T कम से कम एक गैर-स्थैतिक डेटा सदस्य के साथ एक संघ है, तो वास्तव में एक भिन्न सदस्य में एक डिफ़ॉल्ट सदस्य initializer है,
  • यदि T एक संघ नहीं है, तो कम से कम एक गैर-स्थैतिक डेटा सदस्य (यदि कोई हो) के साथ प्रत्येक अनाम संघ सदस्य के लिए, एक गैर-स्थैतिक डेटा सदस्य के पास एक डिफ़ॉल्ट सदस्य आरंभक है, और
  • टी का प्रत्येक संभावित निर्मित बेस क्लास कॉन्स्टेबल-डिफॉल्ट-कंस्ट्रक्टेबल है।

यदि कोई प्रोग्राम किसी कॉन्स्टेबल-योग्य प्रकार T के ऑब्जेक्ट के डिफॉल्ट-इनिशियलाइज़ेशन के लिए कहता है, तो T एक कॉन्स्टेबल-डिफॉल्टेबल-क्लास टाइप या एरे होगा।

इस शब्द का अर्थ अनिवार्य रूप से यह है कि स्पष्ट कोड काम करता है। यदि आप अपने सभी ठिकानों और सदस्यों को इनिशियलाइज़ करते हैं, तो आप यह कह सकते हैं कि A const a;आप किसी भी कंस्ट्रक्टर को कैसे या किस तरह से जादू करते हैं।

struct A {
};
A const a;

gcc ने 4.6.4 से इसे स्वीकार कर लिया है। क्लेंग ने 3.9.0 के बाद से इसे स्वीकार किया है। विजुअल स्टूडियो भी इसे स्वीकार करता है (कम से कम 2017 में, निश्चित नहीं है कि जल्द ही हो)।


3
लेकिन यह अभी भी struct A { int n; A() = default; }; const A a;अनुमति देते समय मना करता है struct B { int n; B() {} }; const B b;क्योंकि नया शब्द अभी भी "उपयोगकर्ता-प्रदान" "उपयोगकर्ता-घोषित" नहीं कहता है और मैं अपना सिर खरोंच कर रहा हूं कि समिति ने इस डीआर से स्पष्ट रूप से डिफ़ॉल्ट डिफ़ॉल्ट निर्माणकर्ताओं को बाहर करने के लिए क्यों चुना, हमें बनाने के लिए मजबूर किया यदि हम असंगठित सदस्यों के साथ कास्ट ऑब्जेक्ट चाहते हैं तो हमारी कक्षाएं गैर-तुच्छ हैं।
२२:११ पर ओकटालिस्ट २०'१

1
दिलचस्प है, लेकिन अभी भी एक किनारे-मामला है जिसे मैंने चलाया है। MyPODपीओडी होने के साथ struct, static MyPOD x;- सदस्य चर (ओं) को उचित रूप से सेट करने के लिए शून्य-आरंभीकरण (क्या यह सही है?) पर निर्भर करता है - संकलन करता है, लेकिन static const MyPOD x;नहीं करता है। क्या कोई मौका है जो तय हो जाएगा?
जोशुआ ग्रीन

66

कारण यह है कि यदि वर्ग में उपयोगकर्ता-परिभाषित कंस्ट्रक्टर नहीं है, तो वह POD हो सकता है, और POD वर्ग डिफ़ॉल्ट रूप से आरंभीकृत नहीं होता है। इसलिए यदि आप POD की एक कांस्टेबल ऑब्जेक्ट घोषित करते हैं जो कि अनइंस्टॉलिज्ड है, तो इसका क्या उपयोग है? इसलिए मुझे लगता है कि मानक इस नियम को लागू करता है ताकि वस्तु वास्तव में उपयोगी हो सके।

struct POD
{
  int i;
};

POD p1; //uninitialized - but don't worry we can assign some value later on!
p1.i = 10; //assign some value later on!

POD p2 = POD(); //initialized

const POD p3 = POD(); //initialized 

const POD p4; //uninitialized  - error - as we cannot change it later on!

लेकिन अगर आप कक्षा को गैर-पीओडी बनाते हैं:

struct nonPOD_A
{
    nonPOD_A() {} //this makes non-POD
};

nonPOD_A a1; //initialized 
const nonPOD_A a2; //initialized 

पीओडी और गैर-पीओडी के बीच अंतर पर ध्यान दें।

उपयोगकर्ता-परिभाषित कंस्ट्रक्टर वर्ग को गैर-पीओडी बनाने का एक तरीका है। ऐसे कई तरीके हैं जिनसे आप ऐसा कर सकते हैं।

struct nonPOD_B
{
    virtual void f() {} //virtual function make it non-POD
};

nonPOD_B b1; //initialized 
const nonPOD_B b2; //initialized 

ध्यान दें कि nonPOD_B उपयोगकर्ता-निर्धारित कंस्ट्रक्टर को परिभाषित नहीं करता है। इसे संकलित करें। यह संकलित करेगा:

और आभासी फ़ंक्शन पर टिप्पणी करें, फिर यह त्रुटि देता है, जैसा कि अपेक्षित है:


खैर, मुझे लगता है, आपने मार्ग को गलत समझा। यह पहली बार यह कहता है (.58.5 / 9):

यदि किसी ऑब्जेक्ट के लिए कोई इनिशियलाइज़र निर्दिष्ट नहीं किया गया है, और ऑब्जेक्ट (संभवतः cv-योग्य) नॉन-पॉड क्लास प्रकार (या इसके प्रकार) का है, तो ऑब्जेक्ट डिफ़ॉल्ट-आरंभीकृत होगा; [...]

यह गैर-पीओडी वर्ग के बारे में बात करता है जो संभवतः सीवी-योग्य प्रकार है। यदि निर्दिष्ट कोई इनिशियलाइज़र नहीं है, तो गैर-पीओडी ऑब्जेक्ट डिफ़ॉल्ट-आरंभिक होगा। और डिफ़ॉल्ट-आरंभिक क्या है ? गैर-पीओडी के लिए, युक्ति कहती है (/8.5 / 5),

टी प्रकार की एक वस्तु को डिफ़ॉल्ट-प्रारंभिक करने के लिए:
- यदि टी एक गैर-पीओडी वर्ग प्रकार (खंड 9) है, तो टी के लिए डिफ़ॉल्ट निर्माणकर्ता को बुलाया जाता है (और यदि टी कोई सुलभ डिफ़ॉल्ट निर्माता नहीं है, तो आरंभीकरण बीमार हो जाता है);

यह केवल T के डिफ़ॉल्ट निर्माता के बारे में बात करता है , चाहे इसका उपयोगकर्ता-परिभाषित या संकलक-जनित अप्रासंगिक हो।

यदि आप इसे स्पष्ट कर रहे हैं, तो समझें कि आगे की युक्ति क्या कहती है ((/8.5 / 9),

[...]; यदि ऑब्जेक्ट कांस्टेबल-योग्य प्रकार का है, तो अंतर्निहित वर्ग प्रकार में एक उपयोगकर्ता-घोषित डिफ़ॉल्ट निर्माता होगा।

तो इस पाठ का तात्पर्य है, यदि वस्तु कांस्टेबल-योग्य POD प्रकार की है, तो प्रोग्राम बीमार हो जाएगा और इसमें कोई इनिशियलाइज़र निर्दिष्ट नहीं है (क्योंकि POD डिफॉल्ट इनिशियलाइज़ नहीं हैं):

POD p1; //uninitialized - can be useful - hence allowed
const POD p2; //uninitialized - never useful  - hence not allowed - error

वैसे, यह ठीक संकलित करता है , क्योंकि इसका गैर-पीओडी, और डिफ़ॉल्ट-प्रारंभिक किया जा सकता है


1
मेरा मानना ​​है कि आपका अंतिम उदाहरण एक संकलित त्रुटि है - nonPOD_Bउपयोगकर्ता के लिए डिफ़ॉल्ट निर्माणकर्ता नहीं है इसलिए लाइन const nonPOD_B b2की अनुमति नहीं है।
कारू

1
कक्षा को गैर-पीओडी बनाने का एक अन्य तरीका यह है कि इसे एक डेटा सदस्य दिया जाए जो कि पीओडी नहीं है (उदाहरण के Bलिए प्रश्न में मेरी संरचना )। लेकिन उपयोगकर्ता-प्रदान की गई डिफ़ॉल्ट निर्माता अभी भी उस स्थिति में आवश्यक है।
कारू

"यदि कोई प्रोग्राम किसी कॉन्स्टेबल-योग्य टाइप T के ऑब्जेक्ट के डिफॉल्ट इनिशियलाइज़ेशन के लिए कहता है, तो T एक क्लास-प्रकार होगा जो यूजर द्वारा प्रदान किया गया डिफॉल्ट कंस्ट्रक्टर होगा।"
कारू

@ कर्कू: मैंने वह पढ़ा है। ऐसा लगता है कि कल्पना में अन्य मार्ग हैं, जो constसंकलक द्वारा उत्पन्न डिफ़ॉल्ट-निर्माता को कॉल करके गैर-पीओडी ऑब्जेक्ट को आरंभ करने की अनुमति देता है ।
नवाज

2
आपके ideone लिंक टूटे हुए प्रतीत होते हैं, और यह बहुत अच्छा होगा यदि यह उत्तर C ++ 11/14 में अपडेट किया जा सकता है क्योंकि P8.5 में POD का उल्लेख नहीं है।
ओकटालिस्ट

12

मेरी ओर से शुद्ध अटकलें, लेकिन विचार करें कि अन्य प्रकारों में भी समान प्रतिबंध है:

int main()
{
    const int i; // invalid
}

इसलिए न केवल यह नियम सुसंगत है, बल्कि यह (पुनरावर्ती) इकाईबद्ध const(उप) वस्तुओं को रोकता है :

struct X {
    int j;
};
struct A {
    int i;
    X x;
}

int main()
{
    const A a; // a.i and a.x.j in unitialized states!
}

प्रश्न के दूसरे पक्ष के लिए (इसे डिफ़ॉल्ट निर्माता के साथ प्रकारों के लिए अनुमति देता है), मुझे लगता है कि यह विचार यह है कि उपयोगकर्ता द्वारा प्रदान किए गए डिफ़ॉल्ट निर्माता के साथ एक प्रकार हमेशा निर्माण के बाद कुछ समझदार स्थिति में होना चाहिए। ध्यान दें कि नियम निम्नलिखित के लिए अनुमति देते हैं:

struct A {
    explicit
    A(int i): initialized(true), i(i) {} // valued constructor

    A(): initialized(false) {}

    bool initialized;
    int i;
};

const A a; // class invariant set up for the object
           // yet we didn't pay the cost of initializing a.i

तब शायद हम एक नियम बना सकते हैं जैसे 'कम से कम एक सदस्य को उपयोगकर्ता द्वारा प्रदान किए गए डिफ़ॉल्ट निर्माणकर्ता में समझदारी से आरंभ किया जाना चाहिए', लेकिन इस तरह से मर्फी से बचाव के लिए बहुत अधिक समय व्यतीत करना होगा। C ++ प्रोग्रामर को कुछ बिंदुओं पर भरोसा करने के लिए प्रेरित करता है।


लेकिन जोड़ने से A(){}, त्रुटि दूर हो जाएगी, इसलिए यह कुछ भी नहीं रोकता है। नियम पुनरावर्ती रूप से काम नहीं करता है - X(){}कभी भी उस उदाहरण के लिए आवश्यक नहीं है।
कारू

2
खैर, कम से कम प्रोग्रामर को एक निर्माता को जोड़ने के लिए मजबूर करके, वह समस्या के बारे में एक मिनट का विचार देने के लिए मजबूर है और शायद गैर-तुच्छ एक के साथ आ रहा है
arne

@ केरू ने केवल आधे प्रश्न को संबोधित किया - तय किया कि :)
लुक डैंटन

4
@ हार्न: केवल समस्या यह है कि यह गलत प्रोग्रामर है। वर्ग को तत्काल करने की कोशिश करने वाला व्यक्ति वह सभी विचार दे सकता है जो वह मामले में चाहता है, लेकिन वे कक्षा को संशोधित करने में सक्षम नहीं हो सकते हैं। क्लास लेखक ने सदस्यों के बारे में सोचा, देखा कि वे सभी अंतर्निहित डिफ़ॉल्ट निर्माता द्वारा समझदारी से शुरू किए गए थे, इसलिए कभी भी एक को नहीं जोड़ा गया।
कारू

3
मानक के इस हिस्से से मैंने जो लिया है वह "हमेशा गैर-पीओडी प्रकारों के लिए हमेशा एक डिफ़ॉल्ट निर्माणकर्ता घोषित करता है, अगर कोई व्यक्ति एक दिन एक बाधा बनाना चाहता है"। यह थोड़ा ज्यादा लगता है।
कारू

3

मैं C ++ 2018 की बैठक में तैमूर डौलर की बात देख रहा था और मुझे आखिरकार एहसास हुआ कि मानक को यहां एक उपयोगकर्ता-घोषित निर्माता की आवश्यकता है, न कि केवल एक उपयोगकर्ता-घोषित की गई। इसे वैल्यू इनिशियलाइजेशन के नियमों के साथ करना है।

दो वर्गों पर विचार करें: Aएक उपयोगकर्ता-घोषित निर्माता है, Bएक उपयोगकर्ता-प्रदान निर्माता है:

struct A {
    int x;
    A() = default;
};
struct B {
    int x;
    B() {}
};

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

  • A a;डिफ़ॉल्ट आरंभीकरण है: सदस्य int xअसिंचित है।
  • B b;डिफ़ॉल्ट आरंभीकरण है: सदस्य int xअसिंचित है।
  • A a{};मूल्य initialisation है: सदस्य int xहै शून्य initialised
  • B b{};मूल्य आरंभीकरण है: सदस्य int xअसिंचित है।

अब देखें कि जब हम जोड़ते हैं तो क्या होता है const:

  • const A a;डिफ़ॉल्ट अभिपुष्टि है: यह प्रश्न में उद्धृत नियम के कारण बीमार है
  • const B b;डिफ़ॉल्ट आरंभीकरण है: सदस्य int xअसिंचित है।
  • const A a{};मूल्य initialisation है: सदस्य int xहै शून्य initialised
  • const B b{};मूल्य आरंभीकरण है: सदस्य int xअसिंचित है।

एक असिंचित constस्केलर (जैसे int xसदस्य) बेकार होगा: इसे लिखना बीमार है (क्योंकि यह है const) और इससे पढ़ना यूबी है (क्योंकि यह अनिश्चित मान रखता है)। तो यह नियम आपको इस तरह की चीज़ बनाने से रोकता है, आपको या तो एक आरंभिक या ऑप्ट-इन जोड़ने के लिए खतरनाक व्यवहार में उपयोगकर्ता द्वारा प्रदान किए गए कंस्ट्रक्टर को जोड़कर।

मुझे लगता है कि [[uninitialized]]जब आप जानबूझकर किसी ऑब्जेक्ट को इनिशियलाइज़ नहीं कर रहे होते हैं, तो कंपाइलर को बताना पसंद करना अच्छा होगा । फिर हम इस कोने के मामले में अपनी कक्षा को सामान्य रूप से डिफ़ॉल्ट रूप से बनाने के लिए मजबूर नहीं होंगे। यह विशेषता वास्तव में प्रस्तावित की गई है , लेकिन अन्य सभी मानक विशेषताओं की तरह, यह किसी भी मानक व्यवहार को अनिवार्य नहीं करता है, केवल संकलक के लिए एक संकेत होने के नाते।


1

बधाई हो, आपने एक ऐसे मामले का आविष्कार किया है जिसमें constघोषणा करने के लिए कोई उपयोगकर्ता परिभाषित कंस्ट्रक्टर नहीं होना चाहिए, जिसका कोई अर्थ नहीं है।

अब आप उस नियम का एक उचित री-वर्डिंग लेकर आ सकते हैं जो आपके मामले को कवर करता है लेकिन फिर भी उन मामलों को बनाता है जो गैरकानूनी होने चाहिए? क्या यह 5 या 6 पैराग्राफ से कम है? क्या यह आसान और स्पष्ट है कि इसे किसी भी स्थिति में कैसे लागू किया जाना चाहिए?

मेरा मानना ​​है कि एक नियम के साथ आने वाला है जो आपके द्वारा बनाई गई घोषणा को वास्तव में कठिन बनाने की अनुमति देता है, और यह सुनिश्चित करना कि नियम को इस तरह से लागू किया जा सकता है कि लोगों को समझ में आता है जब कोड पढ़ना और भी कठिन हो। मैं कुछ हद तक प्रतिबंधात्मक नियम पसंद करूंगा जो ज्यादातर मामलों में एक बहुत ही बारीक और जटिल नियम के लिए करना सही था जिसे समझना और लागू करना मुश्किल था।

सवाल यह है कि क्या कोई सम्मोहक कारण है कि नियम अधिक जटिल होना चाहिए? क्या कोई ऐसा कोड है जिसे लिखना या समझना बहुत मुश्किल होगा, जिसे नियम के अधिक जटिल होने पर बहुत अधिक लिखा जा सकता है?


1
यहाँ मेरा सुझाव दिया गया शब्द है: "यदि कोई प्रोग्राम किसी कास्ट-योग्य प्रकार T के ऑब्जेक्ट के डिफ़ॉल्ट इनिशियलाइज़ेशन के लिए कहता है, तो T एक गैर-POD क्लास प्रकार होगा।" यह const POD x;गैरकानूनी होगा जैसा const int x;कि अवैध है (जो समझ में आता है, क्योंकि यह एक पीओडी के लिए बेकार है), लेकिन const NonPOD x;कानूनी बनाओ (जो समझ में आता है, क्योंकि इसमें उपयोगी कंस्ट्रक्टर / डिस्ट्रक्टर्स वाले उप-विषय हो सकते हैं, या एक उपयोगी कंस्ट्रक्टर या विध्वंसक खुद हो सकता है) ।
कारू

@ कुरु - वह शब्द काम कर सकता है। मुझे RFC मानकों का उपयोग किया जाता है, और इसलिए मुझे लगता है कि 'T होना चाहिए' को 'T होना चाहिए' पढ़ना चाहिए। लेकिन हां, यह काम कर सकता है।
सर्वव्यापी

@ केरू - स्ट्रक्चर नॉनपॉड के बारे में {int i; आभासी शून्य च () {}}? इसका मतलब नॉन नॉनपॉड एक्स बनाने से नहीं है; कानूनी।
ग्रूज़ोवेटर

1
@gruzovator यदि आपके पास एक खाली उपयोगकर्ता-घोषित डिफ़ॉल्ट निर्माता है, तो क्या इससे कोई मतलब होगा? मेरा सुझाव सिर्फ मानक की एक व्यर्थ आवश्यकता को दूर करने का प्रयास करता है; इसके साथ या इसके बिना अभी भी कोड लिखने के कई तरीके हैं जो समझ में नहीं आते हैं।
कारु

1
@ कुरु मैं आपसे सहमत हूँ। मानक में इस नियम के कारण कई वर्ग हैं जिन्हें उपयोगकर्ता द्वारा परिभाषित खाली कंस्ट्रक्टर रखना पड़ता है । मुझे जीसीसी व्यवहार पसंद है। यह उदाहरण struct NonPod { std::string s; }; const NonPod x;देता है और जब NonPod है तो एक त्रुटि देता हैstruct NonPod { int i; std::string s; }; const NonPod x;
gruzovator
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.