कैसे एक का उपयोग करना चाहिए std :: वैकल्पिक?


133

मैं इसका दस्तावेजीकरण पढ़ रहा हूं std::experimental::optionalऔर मेरे पास इसके बारे में एक अच्छा विचार है कि यह क्या करता है, लेकिन मुझे समझ में नहीं आता है कि मुझे इसका उपयोग कब करना चाहिए या मुझे इसका उपयोग कैसे करना चाहिए। इस साइट में अभी तक कोई उदाहरण नहीं है जो इस वस्तु की वास्तविक अवधारणा को समझने के लिए मेरे लिए कठिन छोड़ देता है। std::optionalउपयोग करने के लिए एक अच्छा विकल्प कब है , और यह उस चीज की भरपाई कैसे करता है जो पिछले मानक (सी ++ 11) में नहीं मिला था।


19
Boost.optional डॉक्स कुछ प्रकाश डाला सकता है।
जुआनकोपंजा

एसटीडी की तरह लगता है :: unique_ptr आम तौर पर समान उपयोग मामलों की सेवा कर सकता है। मुझे लगता है कि यदि आपके पास नई चीज़ है, तो वैकल्पिक बेहतर हो सकता है, लेकिन मुझे लगता है कि (डेवलपर्स | ऐप्स) नए पर इस तरह के दृश्य एक छोटे से अल्पसंख्यक में हैं ... AFAICT, वैकल्पिक एक महान विचार नहीं है। बहुत कम से कम, हम में से अधिकांश आराम से इसके बिना रह सकते थे। व्यक्तिगत रूप से, मैं एक ऐसी दुनिया में अधिक आरामदायक रहूंगा जहां मुझे unique_ptr बनाम वैकल्पिक के बीच चयन नहीं करना है। मुझे पागल कहो, लेकिन पायथन का ज़ेन सही है: कुछ करने का एक सही तरीका है!
allyourcode

19
हम हमेशा नष्ट किए जाने वाले ढेर पर कुछ आवंटित नहीं करना चाहते हैं, इसलिए कोई भी अनूठे_ptr वैकल्पिक का विकल्प नहीं है।
क्रि

5
@allyourcode कोई सूचक इसका विकल्प नहीं है optional। कल्पना कीजिए कि आप optional<int>या चाहते हैं <char>। क्या आपको वास्तव में लगता है कि "ज़ेन" को गतिशील रूप से आवंटित, प्रसार, और फिर हटाने के लिए आवश्यक है - ऐसा कुछ जो अन्यथा किसी भी आवंटन की आवश्यकता नहीं कर सकता है और स्टैक पर कॉम्पैक्ट रूप से फिट हो सकता है, और संभवतः एक रजिस्टर में भी?
अंडरस्कोर_ड

1
एक अन्य अंतर, जिसके परिणामस्वरूप निहित मूल्य के ढेर बनाम ढेर से कोई संदेह नहीं है, यह है कि टेम्पलेट तर्क मामले में एक अपूर्ण प्रकार नहीं हो सकता है std::optional(जबकि यह हो सकता है std::unique_ptr)। अधिक सटीक रूप से, मानक के लिए आवश्यक है कि T [...] विनाशकारी की आवश्यकताओं को पूरा करेगा
dan_din_pantelimon

जवाबों:


172

इसका सबसे सरल उदाहरण मैं सोच सकता हूं:

std::optional<int> try_parse_int(std::string s)
{
    //try to parse an int from the given string,
    //and return "nothing" if you fail
}

एक ही बात को एक संदर्भ तर्क के साथ पूरा किया जा सकता है (जैसा कि निम्नलिखित हस्ताक्षर में), लेकिन उपयोग std::optionalहस्ताक्षर और उपयोग को अच्छा बनाता है।

bool try_parse_int(std::string s, int& i);

यह किया जा सकता है कि एक और तरीका विशेष रूप से बुरा है :

int* try_parse_int(std::string s); //return nullptr if fail

इसके लिए गतिशील मेमोरी आवंटन, स्वामित्व की चिंता, आदि की आवश्यकता होती है - हमेशा ऊपर दिए गए अन्य दो हस्ताक्षरों में से एक को प्राथमिकता दें।


एक और उदाहरण:

class Contact
{
    std::optional<std::string> home_phone;
    std::optional<std::string> work_phone;
    std::optional<std::string> mobile_phone;
};

यह std::unique_ptr<std::string>प्रत्येक फोन नंबर के लिए कुछ पसंद करने के बजाय बेहद बेहतर है ! std::optionalआपको डेटा स्थानीयता देता है, जो प्रदर्शन के लिए बहुत अच्छा है।


एक और उदाहरण:

template<typename Key, typename Value>
class Lookup
{
    std::optional<Value> get(Key key);
};

यदि लुकअप में एक निश्चित कुंजी नहीं है, तो हम बस "कोई मूल्य नहीं" वापस कर सकते हैं।

मैं इसे इस तरह से उपयोग कर सकता हूं:

Lookup<std::string, std::string> location_lookup;
std::string location = location_lookup.get("waldo").value_or("unknown");

एक और उदाहरण:

std::vector<std::pair<std::string, double>> search(
    std::string query,
    std::optional<int> max_count,
    std::optional<double> min_match_score);

यह चार फ़ंक्शन अधिभार के साथ max_count(या नहीं) और min_match_score(या नहीं) का हर संभव संयोजन लेने की तुलना में बहुत अधिक समझ में आता है ।

यह भी समाप्त शापित "दर्रा -1के लिए max_countअगर आप एक सीमा नहीं करना चाहता" या "दर्रा std::numeric_limits<double>::min()के लिए min_match_scoreयदि आप एक न्यूनतम स्कोर नहीं करना चाहती"!


एक और उदाहरण:

std::optional<int> find_in_string(std::string s, std::string query);

यदि क्वेरी स्ट्रिंग में नहीं है s, तो मैं "नहीं int" चाहता हूं - इस उद्देश्य (-1) के लिए किसी विशेष मूल्य का उपयोग करने का फैसला नहीं किया।


अतिरिक्त उदाहरणों के लिए, आप boost::optional दस्तावेज़ देख सकते हैं । boost::optionalऔर std::optionalमूल रूप से व्यवहार और उपयोग के मामले में समान होगा।


13
@gnzlbg std::optional<T>सिर्फ एक Tऔर एक है bool। सदस्य फ़ंक्शन कार्यान्वयन अत्यंत सरल हैं। इसका उपयोग करते समय प्रदर्शन वास्तव में एक चिंता का विषय नहीं होना चाहिए - ऐसे समय होते हैं जब कुछ वैकल्पिक होता है, इस मामले में यह अक्सर नौकरी के लिए सही उपकरण होता है।
टिमोथी

8
@TimothyShields std::optional<T>इससे कहीं अधिक जटिल है। यह newअन्य चीजों के constexprबीच एक शाब्दिक प्रकार (यानी के साथ उपयोग ) करने के लिए उचित संरेखण और आकार के साथ प्लेसमेंट और बहुत अधिक अन्य चीजों का उपयोग करता है । भोला Tऔर boolदृष्टिकोण बहुत जल्दी विफल हो जाएगा।
रैपर्ट्स

15
@Rapptz लाइन 256: union storage_t { unsigned char dummy_; T value_; ... }पंक्ति 289: struct optional_base { bool init_; storage_t<T> storage_; ... }कैसे वह यह है कि नहीं "एक Tऔर एक bool"? मैं इस बात से पूरी तरह सहमत हूं कि कार्यान्वयन बहुत मुश्किल और अनौपचारिक है, लेकिन वैचारिक रूप से और संक्षिप्त रूप से टाइप ए Tऔर ए है bool। "भोला Tऔर boolदृष्टिकोण बहुत जल्दी विफल हो जाएगा।" कोड को देखते हुए आप यह कैसे बयान कर सकते हैं?
टिमोथी शील्ड

12
@ रेपट्ज़ यह अभी भी एक बूल और एक इंट के लिए जगह है। संघ केवल वैकल्पिक बनाने के लिए एक टी नहीं है अगर यह वास्तव में नहीं चाहता है। यह अभी भी struct{bool,maybe_t<T>}संघ है बस ऐसा नहीं करना है struct{bool,T}जो सभी मामलों में एक टी का निर्माण करेगा।
पीटर

12
@allyourcode बहुत अच्छा सवाल। दोनों std::unique_ptr<T>और std::optional<T>कुछ अर्थों में "एक वैकल्पिक" की भूमिका को पूरा करते हैं T। मैं उनके बीच के अंतर को "कार्यान्वयन विवरण" के रूप में वर्णित करूंगा: अतिरिक्त आवंटन, मेमोरी प्रबंधन, डेटा स्थानीयता, चाल की लागत, आदि। std::unique_ptr<int> try_parse_int(std::string s);उदाहरण के लिए मेरे पास कभी नहीं होगा , क्योंकि यह हर कॉल के लिए आवंटन का कारण होगा, भले ही इसका कोई कारण न हो। । मेरे पास कभी भी एक वर्ग नहीं होगा std::unique_ptr<double> limit;- एक आवंटन क्यों होता है और डेटा स्थानीयता खो देता है?
टिमोथी ने 21

35

एक उदाहरण नए अपनाया कागज से उद्धृत किया गया है: N3672, std :: वैकल्पिक :

 optional<int> str2int(string);    // converts int to string if possible

int get_int_from_user()
{
     string s;

     for (;;) {
         cin >> s;
         optional<int> o = str2int(s); // 'o' may or may not contain an int
         if (o) {                      // does optional contain a value?
            return *o;                  // use the value
         }
     }
}

13
चूँकि आप जानकारी प्राप्त कर सकते हैं कि क्या आपको int"त्रुटि" अर्थ रखने के लिए कुछ "प्रेत" मान "" के आस-पास से गुजरने के बजाय कॉल पदानुक्रम मिला या नहीं।
लुइस माचूका

1
@ यह वास्तव में एक महान उदाहरण है। यह (ए) str2int()रूपांतरण को लागू करने की अनुमति देता है लेकिन यह चाहता है, (बी) प्राप्त करने की विधि की परवाह किए बिना string s, और (सी) optional<int>कुछ बेवकूफ जादू-संख्या, bool/ संदर्भ, या डायनामिक-आवंटन के आधारित तरीके के बजाय पूर्ण अर्थ बताता है इसे कर रहा हूँ।
अंडरस्कोर_ड

10

लेकिन मुझे समझ नहीं आता कि मुझे इसका उपयोग कब करना चाहिए या मुझे इसका उपयोग कैसे करना चाहिए।

विचार करें कि जब आप एक एपीआई लिख रहे हैं और आप यह व्यक्त करना चाहते हैं कि "रिटर्न नहीं होना" मूल्य एक त्रुटि नहीं है। उदाहरण के लिए, आपको सॉकेट से डेटा पढ़ने की आवश्यकता है, और जब एक डेटा ब्लॉक पूरा हो जाता है, तो आप इसे पार्स करते हैं और इसे वापस करते हैं:

class YourBlock { /* block header, format, whatever else */ };

std::optional<YourBlock> cache_and_get_block(
    some_socket_object& socket);

यदि जोड़ा गया डेटा पार्स करने योग्य ब्लॉक पूरा करता है, तो आप इसे प्रोसेस कर सकते हैं; अन्यथा, पढ़ते रहें और डेटा संलग्न करते रहें:

void your_client_code(some_socket_object& socket)
{
    char raw_data[1024]; // max 1024 bytes of raw data (for example)
    while(socket.read(raw_data, 1024))
    {
        if(auto block = cache_and_get_block(raw_data))
        {
            // process *block here
            // then return or break
        }
        // else [ no error; just keep reading and appending ]
    }
}

संपादित करें: अपने बाकी सवालों के बारे में:

जब std :: वैकल्पिक एक अच्छा विकल्प का उपयोग करें

  • जब आप किसी मूल्य की गणना करते हैं और उसे वापस करने की आवश्यकता होती है, तो बेहतर अर्थार्थक के लिए आउटपुट मूल्य का संदर्भ लेने की तुलना में मूल्य से वापस लौटना पड़ता है (जो उत्पन्न नहीं हो सकता है)।

  • जब आप यह सुनिश्चित करना चाहते हैं कि क्लाइंट कोड को आउटपुट मान की जांच करनी है (जो कोई भी क्लाइंट कोड लिखता है वह त्रुटि के लिए जांच नहीं कर सकता है - यदि आप अन-इनिशियलाइज्ड पॉइंटर का उपयोग करने का प्रयास करते हैं, तो आपको कोर डंप मिलता है; यदि आप अन-का उपयोग करने का प्रयास करते हैं; आरंभीकृत एसटीडी :: वैकल्पिक, आपको एक कैच-सक्षम अपवाद मिलता है)।

[...] और जो पिछले मानक (C ++ 11) में नहीं मिला था उसकी भरपाई कैसे करता है।

C ++ 11 से पहले, आपको "फ़ंक्शन जो मान नहीं लौटा सकता है" के लिए एक अलग इंटरफ़ेस का उपयोग करना था - या तो पॉइंटर द्वारा लौटें और NULL के लिए जांचें, या आउटपुट पैरामीटर स्वीकार करें और "उपलब्ध नहीं" के लिए एक त्रुटि / परिणाम कोड लौटाएं। "।

ग्राहक इसे लागू करने के लिए ग्राहक के अतिरिक्त प्रयास और ध्यान को सही तरीके से लगाते हैं और दोनों भ्रम का एक स्रोत हैं (पहला ग्राहक कार्यान्वयनकर्ता को एक आबंटन के रूप में एक ऑपरेशन के बारे में सोचने के लिए और क्लाइंट कोड को पॉइंटर-हैंडलिंग लॉजिक को लागू करने के लिए आवश्यक है और दूसरा अनुमति देता है ग्राहक कोड अमान्य / अनधिकृत मानों का उपयोग करने से दूर होने के लिए)।

std::optional पिछले समाधानों के साथ आने वाली समस्याओं का ध्यान रखता है।


मुझे पता है कि वे मूल रूप से समान हैं, लेकिन आप boost::इसके बजाय क्यों उपयोग कर रहे हैं std::?
0x499602D2

4
धन्यवाद - मैंने इसे सही किया (मैंने इसका उपयोग किया boost::optionalक्योंकि उपयोग करने के लगभग दो वर्षों के बाद, यह मेरे पूर्व-ललाट कोर्टिकल में कठोर-कोडित है)।
utnapistim

1
यह मुझे एक खराब उदाहरण के रूप में प्रभावित करता है, क्योंकि यदि कई ब्लॉक पूरे हो गए थे तो केवल एक ही वापस किया जा सकता था और बाकी को छोड़ दिया गया था। इसके बजाय फ़ंक्शन को ब्लॉकों का संभवतः-खाली संग्रह वापस करना चाहिए।
बेन वोयगेट

आप सही हे; यह एक गरीब उदाहरण था; मैंने "यदि पहले उपलब्ध हो तो पार्स ब्लॉक" के लिए अपना उदाहरण संशोधित किया है।
utnapistim

4

मैं अक्सर कॉन्फ़िगरेशन फ़ाइलों से खींचे गए वैकल्पिक डेटा का प्रतिनिधित्व करने के लिए वैकल्पिक का उपयोग करता हूं, यह कहना है कि जहां डेटा (जैसे कि अपेक्षित के साथ, अभी तक आवश्यक नहीं है, एक XML दस्तावेज़ के भीतर तत्व) वैकल्पिक रूप से प्रदान किया जाता है, ताकि मैं स्पष्ट रूप से और स्पष्ट रूप से दिखा सकूं डेटा वास्तव में XML दस्तावेज़ में मौजूद था। खासकर जब डेटा में "सेट नहीं" स्थिति हो सकती है, बनाम "खाली" और "सेट" स्थिति (फजी लॉजिक)। वैकल्पिक के साथ, सेट और सेट स्पष्ट नहीं है, खाली भी 0 या शून्य के मान के साथ स्पष्ट होगा।

यह दिखा सकता है कि "सेट नहीं" का मूल्य "खाली" के बराबर कैसे नहीं है। अवधारणा में, एक इंट (int * p) के लिए एक पॉइंटर यह दिखा सकता है, जहां एक नल (p == 0) सेट नहीं है, 0 (* p == 0) का मान सेट है और खाली है, और कोई अन्य मान (* p <> 0) मान पर सेट है।

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

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

मेरा मानना ​​है कि यह कोड स्पष्टता के बारे में है। स्पष्टता कोड रखरखाव और विकास की लागत को कम करती है। कोड के इरादे की स्पष्ट समझ अविश्वसनीय रूप से मूल्यवान है।

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

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