क्या C ++ 11 यूनिफ़ॉर्म इनिशियलाइज़ेशन पुराने स्टाइल सिंटैक्स के लिए प्रतिस्थापन है?


172

मैं समझता हूं कि C ++ 11 की यूनिफॉर्म इनिशियलाइज़ेशन भाषा में कुछ वाक्यात्मक अस्पष्टता को हल करती है, लेकिन बहुत सारे ब्रेज़ेन स्ट्रॉस्ट्रुप की प्रस्तुतियों में (विशेष रूप से गोइंगनेटिव 2012 वार्ता के दौरान), उनके उदाहरण मुख्य रूप से अब इस सिंटैक्स का उपयोग करते हैं जब भी वह वस्तुओं का निर्माण कर रहा होता है।

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

ध्यान दें कि मैं अपने उपयोग के मामले के रूप में मुख्य रूप से ऑब्जेक्ट निर्माण के बारे में सोच रहा हूं, लेकिन अगर विचार करने के लिए अन्य परिदृश्य हैं तो कृपया मुझे बताएं।


यह Programmers.se पर बेहतर चर्चा वाला विषय हो सकता है। यह गुड सब्जेक्टिव पक्ष की ओर झुकाव लगता है।
निकोल बोल

6
@ निचलोलस: दूसरी ओर, आपका उत्कृष्ट उत्तर c ++ - faq टैग के लिए एक बहुत अच्छा उम्मीदवार हो सकता है। मुझे नहीं लगता कि हमने पहले इस पोस्ट के लिए कोई स्पष्टीकरण दिया है।
Matthieu M.

जवाबों:


233

कोडिंग शैली अंततः व्यक्तिपरक है, और यह अत्यधिक संभावना नहीं है कि इससे पर्याप्त प्रदर्शन लाभ होगा। लेकिन यहाँ मैं क्या कहूँगा कि आप एकसमान इनिशियलाइज़ेशन के उदार उपयोग से लाभान्वित होंगे:

अतिरेक टाइपनामों को कम करता है

निम्नलिखित को धयान मे रखते हुए:

vec3 GetValue()
{
  return vec3(x, y, z);
}

मुझे vec3दो बार टाइप करने की आवश्यकता क्यों है ? क्या इसका कोई मतलब है? कंपाइलर अच्छी तरह से जानता है कि फ़ंक्शन क्या देता है। मैं बस यह क्यों नहीं कह सकता, "जो मैं इन मूल्यों के साथ लौटता हूं, उसे वापस लौटाने के निर्माता को बुलाऊं?" यूनिफ़ॉर्म इनिशियलाइज़ेशन के साथ, मैं कर सकता हूँ:

vec3 GetValue()
{
  return {x, y, z};
}

सब कुछ काम करता है।

फ़ंक्शन तर्कों के लिए भी बेहतर है। इस पर विचार करो:

void DoSomething(const std::string &str);

DoSomething("A string.");

यह टाइप किएनाम टाइप किए बिना काम करता है, क्योंकि std::stringएक const char*अंतर्निहित रूप से खुद का निर्माण करना जानता है । एक दम बढ़िया। लेकिन क्या होगा अगर वह स्ट्रिंग से आया, रैपिडएक्सएमएल कहो। या एक लुआ स्ट्रिंग। यही है, चलो कहते हैं कि मैं वास्तव में सामने की स्ट्रिंग की लंबाई जानता हूं। std::stringनिर्माता है कि एक लेता है const char*स्ट्रिंग की लंबाई लेने के लिए करता है, तो मैं सिर्फ एक पारित करना होगा const char*

एक अधिभार है जो स्पष्ट रूप से एक लंबाई लेता है। लेकिन इसका उपयोग करने के लिए, मुझे यह करना होगा DoSomething(std::string(strValue, strLen)):। क्यों वहाँ में अतिरिक्त टाइपनेम है? कंपाइलर जानता है कि प्रकार क्या है। बस के साथ की तरह auto, हम अतिरिक्त टाइपनेम होने से बच सकते हैं:

DoSomething({strValue, strLen});

यह सिर्फ काम करता है। कोई टाइपनेम, कोई उपद्रव, कुछ नहीं। संकलक अपना काम करता है, कोड छोटा होता है, और हर कोई खुश होता है।

दी गई, यह तर्क दिया जाना है कि पहला संस्करण ( DoSomething(std::string(strValue, strLen))) अधिक सुपाठ्य है। यही है, यह स्पष्ट है कि क्या चल रहा है और कौन क्या कर रहा है। यह सच है, एक हद तक; यूनिफॉर्म इनिशियलाइज़ेशन-आधारित कोड को समझने के लिए फ़ंक्शन प्रोटोटाइप को देखने की आवश्यकता होती है। यही कारण है कि कुछ का कहना है कि आपको गैर-कॉन्स्टेंस संदर्भ द्वारा कभी भी मापदंडों को पास नहीं करना चाहिए: ताकि आप कॉल साइट पर देख सकें कि क्या कोई मूल्य संशोधित किया जा रहा है।

लेकिन उसी के लिए कहा जा सकता है auto; यह जानने से कि आपको किसकी auto v = GetSomething();परिभाषा देखने की आवश्यकता है GetSomething। लेकिन जब तक आप इसके पास नहीं autoपहुंचते हैं, तब तक इसे लापरवाह परित्याग के पास इस्तेमाल करने से नहीं रोका जाता है। व्यक्तिगत रूप से, मुझे लगता है कि जब आप इसकी आदत डाल लेंगे तो यह ठीक हो जाएगा। खासतौर पर एक अच्छी IDE के साथ।

मोस्ट वैक्सिंग पार्स कभी नहीं

यहाँ कुछ कोड है।

class Bar;

void Func()
{
  int foo(Bar());
}

पॉप क्विज: क्या है foo? यदि आपने "एक चर" उत्तर दिया है, तो आप गलत हैं। यह वास्तव में एक फ़ंक्शन का प्रोटोटाइप है जो अपने पैरामीटर को एक फ़ंक्शन के रूप में लेता है जो एक रिटर्न देता है Bar, और fooफ़ंक्शन का रिटर्न वैल्यू एक इंट है।

इसे C ++ का "Most Vexing Parse" कहा जाता है क्योंकि यह मनुष्य के लिए बिल्कुल कोई मतलब नहीं रखता है। लेकिन सी ++ के नियमों को दुख की आवश्यकता है: यदि इसे संभवतः फ़ंक्शन प्रोटोटाइप के रूप में व्याख्या किया जा सकता है, तो यह होगा । समस्या यह है Bar(); यह दो चीजों में से एक हो सकता है। यह एक प्रकार का नाम हो सकता है Bar, जिसका अर्थ है कि यह एक अस्थायी निर्माण कर रहा है। या यह एक ऐसा कार्य हो सकता है जो कोई पैरामीटर नहीं लेता है और एक रिटर्न देता है Bar

समान आरंभीकरण को फ़ंक्शन प्रोटोटाइप के रूप में व्याख्या नहीं किया जा सकता है:

class Bar;

void Func()
{
  int foo{Bar{}};
}

Bar{}हमेशा एक अस्थायी बनाता है। int foo{...}हमेशा एक चर बनाता है।

ऐसे कई मामले हैं जहां आप उपयोग करना चाहते हैं, Typename()लेकिन केवल C ++ के पार्सिंग नियमों के कारण नहीं कर सकते। के साथ Typename{}, कोई अस्पष्टता नहीं है।

कारण नहीं

आपके द्वारा दी जाने वाली एकमात्र वास्तविक शक्ति संकीर्ण है। आप यूनिफ़ॉर्म इनिशियलाइज़ेशन के साथ एक बड़े मूल्य के साथ एक छोटे मूल्य को इनिशियलाइज़ नहीं कर सकते हैं।

int val{5.2};

वह संकलन नहीं करेगा। आप पुराने जमाने की इनिशियलाइज़ेशन के साथ कर सकते हैं, लेकिन यूनिफ़ॉर्म इनिशियलाइज़ेशन नहीं।

यह वास्तव में काम करने के लिए शुरुआती सूची बनाने के लिए किया गया था। अन्यथा, शुरुआती सूची के प्रकारों के संबंध में बहुत सारे अस्पष्ट मामले होंगे।

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

ध्यान दें कि कंपाइलर्स आम तौर पर आपको इस तरह की चीज़ों के बारे में चेतावनी देते हैं अगर आपका चेतावनी स्तर अधिक है। तो वास्तव में, यह सब चेतावनी को एक लागू त्रुटि में बदल देता है। कुछ लोग कह सकते हैं कि आपको ऐसा करना चाहिए;)

ऐसा नहीं करने के लिए एक और कारण है:

std::vector<int> v{100};

यह क्या करता है? यह vector<int>एक सौ डिफ़ॉल्ट निर्मित वस्तुओं के साथ बना सकता है । या यह vector<int>1 आइटम के साथ बना सकता है, जिसका मूल्य है 100। दोनों सैद्धांतिक रूप से संभव हैं।

वास्तविकता में, यह उत्तरार्द्ध करता है।

क्यों? प्रारंभिक सूचियाँ समान रूप से समान सिंटैक्स का उपयोग करती हैं। तो यह बताने के लिए कुछ नियम हैं कि अस्पष्टता के मामले में क्या करना है। नियम बहुत सरल है: यदि कंपाइलर इनिशियल लिस्ट कंस्ट्रक्टर का ब्रेस-इनिशियलाइज़ लिस्ट के साथ उपयोग कर सकता है , तो यह होगा । चूंकि vector<int>एक प्रारंभिक सूची निर्माणकर्ता को लगती है initializer_list<int>, और {100} वैध हो सकता है initializer_list<int>, इसलिए यह होना चाहिए

आकार देने वाले निर्माणकर्ता को प्राप्त करने के लिए, आपको ()इसके बजाय उपयोग करना होगा {}

ध्यान दें कि अगर यह vectorएक पूर्णांक के लिए परिवर्तनीय नहीं था, तो ऐसा कुछ नहीं होगा। एक initializer_list उस vectorप्रकार के initializer सूची निर्माता को फिट नहीं करेगा , और इसलिए कंपाइलर अन्य कंस्ट्रक्टरों से लेने के लिए स्वतंत्र होगा।


11
+1 किया। मैं अपना उत्तर हटा रहा हूं क्योंकि आप सभी समान बिंदुओं को बहुत अधिक विस्तार से संबोधित करते हैं।
आर। मार्टिनो फर्नांडिस

21
आखिरी बिंदु यह है कि मैं वास्तव में क्यों चाहूंगा std::vector<int> v{100, std::reserve_tag};। इसी के साथ std::resize_tag। यह वर्तमान में वेक्टर अंतरिक्ष आरक्षित करने के लिए दो कदम उठाता है।
XIO

6
@ नाइकोलोलस - दो बिंदु: मुझे लगा कि वेजिंग पार्स के साथ समस्या फू () थी, बार () नहीं। दूसरे शब्दों में, यदि आपने किया int foo(10), तो क्या आप एक ही समस्या में नहीं चलेंगे? दूसरे, इसका उपयोग न करने का एक और कारण इंजीनियरिंग से अधिक का एक मुद्दा है, लेकिन क्या होगा यदि हम अपनी सभी वस्तुओं का उपयोग कर निर्माण करते हैं {}, लेकिन एक दिन बाद सड़क पर मैं इनिशियलाइज़ेशन सूचियों के लिए एक कंस्ट्रक्टर जोड़ देता हूं? अब मेरे सभी निर्माण कथन इनिशियलाइज़र सूची विवरणों में बदल जाते हैं। रिफैक्टिंग के मामले में बहुत नाजुक लगता है। इस पर कोई टिप्पणी?
void.pointer

7
@RobertDailey: "यदि आपने किया int foo(10), तो क्या आप एक ही समस्या में नहीं चलेंगे?" नंबर 10 एक पूर्णांक शाब्दिक है, और पूर्णांक शाब्दिक कभी भी एक टाइपनेम नहीं हो सकता है। Bar()वेजिंग पार्स इस तथ्य से आता है कि टाइपनेम या अस्थायी मूल्य हो सकता है। यही कंपाइलर के लिए अस्पष्टता पैदा करता है।
निकोल बोल

8
unpleasant behavior- याद रखने के लिए एक नया मानक शब्द है:>
sehe

64

मैं निकोल Bolas 'जवाब के अनुभाग से असहमत लिए जा रहा हूँ कम करता बेमानी Typenames । क्योंकि कोड एक बार लिखा गया है और कई बार पढ़ा गया है, हमें कोड को पढ़ने और समझने में लगने वाले समय को कम से कम करने की कोशिश करनी चाहिए , न कि कोड लिखने में जितना समय लगता है । केवल टाइपिंग को कम से कम करने की कोशिश गलत चीज़ को अनुकूलित करने की कोशिश कर रही है।

निम्नलिखित कोड देखें:

vec3 GetValue()
{
  <lots and lots of code here>
  ...
  return {x, y, z};
}

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

और यहाँ फिर से किसी के लिए पहली बार कोड पढ़ने के लिए यह समझना आसान नहीं है कि वास्तव में क्या निर्माण किया जा रहा है:

void DoSomething(const std::string &str);
...
const char* strValue = ...;
size_t strLen = ...;

DoSomething({strValue, strLen});

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

void DoSomething(const CoolStringType& str);

अगर CoolStringType में एक कंस्ट्रक्टर होता है, जो एक const char * लेता है और size_t (जैसे std :: string करता है) तो DoSomething ({strValue, strLen}) पर कॉल करने पर अस्पष्टता का परिणाम होगा।

वास्तविक प्रश्न का मेरा उत्तर:
नहीं, यूनिफ़ॉर्म इनिशियलाइज़ेशन को पुराने स्टाइल कंस्ट्रक्टर सिंटैक्स के प्रतिस्थापन के रूप में नहीं सोचा जाना चाहिए।

और मेरा तर्क यह है:
यदि दो कथनों में एक ही तरह का इरादा नहीं है, तो उन्हें एक जैसा नहीं दिखना चाहिए। ऑब्जेक्ट इनिशियलाइज़ेशन की दो तरह की धारणाएँ हैं:
1) इन सभी वस्तुओं को लें और उन्हें इस ऑब्जेक्ट में डालें जो मैं इनिशियलाइज़ कर रहा हूँ।
2) एक गाइड के रूप में प्रदान किए गए इन तर्कों का उपयोग करके इस ऑब्जेक्ट का निर्माण करें।

धारणा # 1 के उपयोग के उदाहरण:

struct Collection
{
    int first;
    char second;
    double third;
};

Collection c {1, '2', 3.0};
std::array<int, 3> a {{ 1, 2, 3 }};
std::map<int, char> m { {1, '1'}, {2, '2'}, {3, '3'} };

धारणा # 2 के उपयोग का उदाहरण:

class Stairs
{
    std::vector<float> stepHeights;

public:
    Stairs(float initHeight, int numSteps, float stepHeight)
    {
        float height = initHeight;

        for (int i = 0; i < numSteps; ++i)
        {
            stepHeights.push_back(height);
            height += stepHeight;
        }
    }
};

Stairs s (2.5, 10, 0.5);

मुझे लगता है कि यह एक बुरी बात है कि नया मानक लोगों को इस तरह सीढ़ियों को शुरू करने की अनुमति देता है:

Stairs s {2, 4, 6};

... क्योंकि वह कंस्ट्रक्टर का अर्थ बताता है। ऐसा लगता है कि शुरू में धारणा # 1 जैसी दिखती है, लेकिन ऐसा नहीं है। यह ऑब्जेक्ट एस में कदम ऊंचाइयों के तीन अलग-अलग मूल्यों को नहीं डाल रहा है, भले ही ऐसा लगता है कि यह है। और इससे भी महत्वपूर्ण बात यह है कि यदि ऊपर की तरह सीढ़ियों का एक पुस्तकालय कार्यान्वयन प्रकाशित किया गया है और प्रोग्रामर इसका उपयोग कर रहे हैं, और फिर यदि लाइब्रेरी कार्यान्वयनकर्ता बाद में सीढ़ियों के लिए एक initializer_list निर्माता जोड़ता है, तो सभी कोड जो यूनिफॉर्म इनिशियलाइज़ेशन के लिए सीढ़ियों का उपयोग कर रहे हैं। सिंटेक्स टूटने वाला है।

मुझे लगता है कि C ++ समुदाय को एक समान सम्मेलन के लिए सहमत होना चाहिए कि यूनिफ़ॉर्म इनिशियलाइज़ेशन का उपयोग कैसे किया जाता है, अर्थात समान रूप से सभी इनिशियलाइज़ेशन पर, या, जैसे कि मैं दृढ़ता से सुझाव देता हूं, इन इनिशियलाइज़ेशन की इन दो धारणाओं को अलग करता है और इस प्रकार प्रोग्रामर के इरादे को पाठक के लिए स्पष्ट करता है। कोड।


AFTERTHOUGHT:
यहाँ एक और कारण है कि आप को यूनिफॉर्म इनिशियलाइज़ेशन को पुराने सिंटैक्स के प्रतिस्थापन के रूप में क्यों नहीं सोचना चाहिए, और आप सभी इनिशियलाइज़ेशन के लिए ब्रेस नोटेशन का उपयोग क्यों नहीं कर सकते हैं:

कहो, प्रतिलिपि बनाने के लिए आपका पसंदीदा वाक्यविन्यास है:

T var1;
T var2 (var1);

अब आपको लगता है कि आपको सभी ब्रेसिज़ को नए ब्रेस सिंटैक्स के साथ बदलना चाहिए ताकि आप अधिक सुसंगत हो सकें (और कोड दिखेगा)। यदि प्रकार T एक समुच्चय है, लेकिन ब्रेसिज़ का उपयोग करने वाला सिंटैक्स काम नहीं करता है:

T var2 {var1}; // fails if T is std::array for example

48
यदि आपके पास "<बहुत सारे और यहाँ बहुत सारे कोड हैं" तो आपके कोड को सिंटैक्स की परवाह किए बिना समझना मुश्किल होगा।
केविन क्लाइन

8
IMO के अलावा यह आपके IDE का कर्तव्य है कि वह आपको बताए कि यह किस प्रकार का है (उदाहरण के लिए होवरिंग)। बेशक, यदि आप एक आईडीई का उपयोग नहीं करते हैं, तो आपने खुद पर बोझ लिया :)
abergmeier

4
@ टॉमी मैं आपके कहे कुछ हिस्सों से सहमत हूँ। हालांकि, autoबनाम स्पष्ट प्रकार की घोषणा की बहस के रूप में एक ही भावना में , मैं एक संतुलन के लिए बहस करूंगा: वर्दी शुरुआतीधारियों ने टेम्पलेट मेटा-प्रोग्रामिंग स्थितियों में बहुत बड़ा समय रॉक किया जहां प्रकार आमतौर पर वैसे भी बहुत स्पष्ट होता है। यह -> decltype(....)सरल ऑनलाइन फ़ंक्शन टेम्प्लेट के लिए उदाहरण के लिए आपके रफ़ू जटिल को दोहराने से बचाएगा (मुझे रोते हुए)।
sehe

5
" लेकिन ब्रेसिज़ का उपयोग करने वाला वाक्यविन्यास काम नहीं करता है यदि टाइप टी एक समुच्चय है: " ध्यान दें कि यह जानबूझकर, अपेक्षित व्यवहार के बजाय मानक में रिपोर्ट की गई खराबी है।
निकोल बोलस

5
"अब, उसे फ़ंक्शन हस्ताक्षर पर वापस स्क्रॉल करना होगा" यदि आपको स्क्रॉल करना है, तो आपका फ़ंक्शन बहुत बड़ा है।
माइल्स राउत

-3

यदि आपके निर्माता merely copy their parametersसंबंधित वर्ग चर in exactly the same orderमें हैं, जिसमें उन्हें कक्षा के अंदर घोषित किया गया है, तो यूनिफॉर्म आरंभीकरण का उपयोग अंततः निर्माणकर्ता को कॉल करने की तुलना में तेज (लेकिन बिल्कुल समान हो सकता है) कर सकता है।

जाहिर है, यह इस तथ्य को नहीं बदलता है कि आपको हमेशा कंस्ट्रक्टर घोषित करना चाहिए।


2
आप क्यों कहते हैं कि यह तेज हो सकता है?
jbcoe

यह गलत है। कंस्ट्रक्टर घोषित करने की आवश्यकता नहीं है struct X { int i; }; int main() { X x{42}; }:। यह भी गलत है, कि यूनिफॉर्म इनिशियलाइज़ेशन वैल्यू इनिशियलाइज़ेशन से अधिक तेज़ हो सकता है।
टिम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.