पास-बाय-वैल्यू और एसटीडी के फायदे :: पास-बाय-रेफरेंस पर जाएं


106

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

मैंने इस क्लास का उपयोग ट्यूटोरियल से किया है:

class Creature
{
private:
    std::string m_name;

public:
    Creature(const std::string &name)
            :  m_name{name}
    {
    }
};

इससे क्लैंग-टिड्डी से एक सुझाव मिलता है कि मुझे संदर्भ और उपयोग के बजाय मूल्य से गुजरना चाहिए std::move। यदि मैं करता हूं, तो मुझे nameएक संदर्भ बनाने के लिए सुझाव मिलता है (यह सुनिश्चित करने के लिए कि हर बार नकल न हो) और चेतावनीstd::move कोई प्रभाव नहीं होगा क्योंकि nameएक है constइसलिए मुझे इसे हटा देना चाहिए।

जिस तरह से मुझे चेतावनी नहीं मिलती है वह एकमात्र तरीका है const पूरी तरह :

Creature(std::string name)
        :  m_name{std::move(name)}
{
}

जो तर्कसंगत लगता है, क्योंकि constमूल स्ट्रिंग के साथ खिलवाड़ को रोकने के लिए इसका एकमात्र लाभ था (जो ऐसा नहीं होता क्योंकि मैं मूल्य से गुजरता था)। लेकिन मैंने CPlusPlus.com पर पढ़ा :

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

अब इस कोड की कल्पना करें:

std::string nameString("Alex");
Creature c(nameString);

क्योंकि nameStringमूल्य से पारित हो जाता है, std::moveकेवल अमान्य होगाname निर्माणकर्ता के अंदर और मूल स्ट्रिंग को नहीं छूएगा। लेकिन इसके क्या फायदे हैं? ऐसा लगता है कि सामग्री किसी भी समय केवल एक बार कॉपी हो जाती है - अगर मैं कॉल करते समय संदर्भ से गुजरता हूं m_name{name}, अगर मैं इसे पास करने पर मूल्य से गुजरता हूं (और फिर यह स्थानांतरित हो जाता है)। मैं समझता हूं कि यह मूल्य से गुजरने और उपयोग न करने से बेहतर है std::move(क्योंकि यह दो बार कॉपी हो जाता है)।

तो दो सवाल:

  1. क्या मैं सही ढंग से समझ पाया कि यहाँ क्या हो रहा है?
  2. वहाँ std::moveसंदर्भ से गुजर रहा है और सिर्फ बुला का उपयोग करने का कोई उल्टा है m_name{name}?

3
संदर्भ के साथ पास के साथ, Creature c("John");एक अतिरिक्त प्रति बनाता है
user253751

1
यह लिंक एक मूल्यवान पढ़ा जा सकता है, यह गुजरता है std::string_viewऔर SSO भी शामिल है।
lubgr

मैंने पाया clang-tidyहै कि अपने आप को पठनीयता की कीमत पर अनावश्यक रूप से सूक्ष्म रूप से अपनाने के लिए एक शानदार तरीका है। यहां सवाल पूछने के लिए, कुछ और से पहले, यह है कि हम कितनी बार वास्तव में Creatureकंस्ट्रक्टर कहते हैं ।
cz

जवाबों:


37
  1. क्या मैं सही ढंग से समझ पाया कि यहाँ क्या हो रहा है?

हाँ।

  1. वहाँ std::moveसंदर्भ से गुजर रहा है और सिर्फ बुला का उपयोग करने का कोई उल्टा है m_name{name}?

किसी भी अतिरिक्त अधिभार के बिना फ़ंक्शन हस्ताक्षर को समझना आसान है। हस्ताक्षर तुरंत पता चलता है कि तर्क की प्रतिलिपि बनाई जाएगी - यह कॉल करने वालों को यह सोचने से बचाता है कि क्या एक const std::string&संदर्भ को डेटा सदस्य के रूप में संग्रहीत किया जा सकता है, संभवतः बाद में एक झूलने वाला संदर्भ बन सकता है। और जब फंक्शन में रूल्स पास किए जाते हैं तो अनावश्यक कॉपियों से बचने के लिए ओवरलोड std::string&& nameऔर const std::string&तर्कों की ज़रूरत नहीं होती है । एक अंतराल गुजर रहा है

std::string nameString("Alex");
Creature c(nameString);

उस फ़ंक्शन को जो मान द्वारा अपने तर्क को लेता है, एक प्रतिलिपि और एक चाल निर्माण का कारण बनता है। एक ही कार्य के लिए एक मार्ग पारित करना

std::string nameString("Alex");
Creature c(std::move(nameString));

दो चाल निर्माण का कारण बनता है। इसके विपरीत, जब फ़ंक्शन पैरामीटर होता है const std::string&, तो हमेशा एक प्रति होगी, यहां तक ​​कि जब एक तर्क तर्क पारित किया जाता है। यह स्पष्ट रूप से एक फायदा है जब तक कि तर्क-निर्माण को स्थानांतरित करने के लिए सस्ता है (यह मामला है)std::string )।

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

void setName(std::string name)
{
    m_name = std::move(name);
}

m_nameपुन: असाइन किए जाने से पहले संदर्भित संसाधन की अस्वीकृति का कारण होगा । मैं प्रभावी आधुनिक C ++ में आइटम 41 को पढ़ने की सलाह देता हूं और यह प्रश्न भी ।


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

1
हाँ, बिलकुल ऐसा ही है। जब m_nameएक const std::string&पैरामीटर से असाइन किया जाता है , तो आंतरिक मेमोरी का उपयोग तब तक किया जाता है जब तक कि m_nameफिट बैठता है। जब मूव-असाइन किया जाता है m_name, तो मेमोरी को पहले से ही निपटाया जाना चाहिए। अन्यथा, असाइनमेंट के दाहिने हाथ की ओर से संसाधनों को "चोरी" करना असंभव था।
lubgr

यह एक झूलने वाला संदर्भ कब बनता है? मुझे लगता है कि आरंभीकरण सूची गहरी प्रतिलिपि का उपयोग करती है।
ली ताईजी

104
/* (0) */ 
Creature(const std::string &name) : m_name{name} { }
  • एक उत्तीर्ण लैवल्यू बांधता है name, फिर उसमें नकल की जाती है m_name

  • एक उत्तीर्ण प्रतिद्वंद्विता को बांधता है name, फिर में नकल की जाती है m_name


/* (1) */ 
Creature(std::string name) : m_name{std::move(name)} { }
  • एक उत्तीर्ण लैवल्यू की नकल की जाती है name, फिर उसे अंदर ले जाया जाता है m_name

  • एक उत्तीर्ण प्रतिद्वंद्विता में ले जाया जाता है name, फिर अंदर ले जाया जाता है m_name


/* (2) */ 
Creature(const std::string &name) : m_name{name} { }
Creature(std::string &&rname) : m_name{std::move(rname)} { }
  • एक उत्तीर्ण लैवल्यू बांधता है name, फिर उसमें नकल की जाती है m_name

  • एक गुज़रा हुआ रस्सा बांधता है rname, फिर अंदर ले जाया जाता है m_name


जैसा कि चाल परिचालन आमतौर पर प्रतियों की तुलना में तेज होता है, (1 ) यदि आप बहुत सी अस्थायीियां पास करते हैं तो (0) से बेहतर है । (2) प्रतियों / चालों के संदर्भ में इष्टतम है, लेकिन कोड पुनरावृत्ति की आवश्यकता है।

सही दोहराव के साथ कोड पुनरावृत्ति से बचा जा सकता है :

/* (3) */
template <typename T,
          std::enable_if_t<
              std::is_convertible_v<std::remove_cvref_t<T>, std::string>, 
          int> = 0
         >
Creature(T&& name) : m_name{std::forward<T>(name)} { }

आप वैकल्पिक Tरूप से उन प्रकार के डोमेन को प्रतिबंधित करने के लिए विवश करना चाहते हैं जिन्हें इस निर्माता द्वारा तत्काल किया जा सकता है (जैसा कि ऊपर दिखाया गया है)। C ++ 20 का लक्ष्य इसे अवधारणाओं के साथ सरल बनाना है ।


C ++ 17 में, प्रतियाँ गारंटीकृत कॉपी एलिसन से प्रभावित होती हैं , जो - जब लागू होती हैं - कार्यों के लिए तर्क पास करते समय प्रतियों / चालों की संख्या को कम कर देगा।


(1) के लिए pr-value और xvalue मामला c ++ 17 no के बाद से समान नहीं है?
ओलिव

1
ध्यान दें कि इस मामले में आगे बढ़ने के लिए आपको SFINAE की आवश्यकता नहीं है । इसकी केवल अवहेलना करने की आवश्यकता है। यह अनुग्राह्यतापूर्वक संभावित त्रुटि संदेश जब ग़लत तर्क पारित करने के लिए उपयोगी
Caleth

@ ऑलिव हां। xvalues ​​को स्थानांतरित करने की आवश्यकता है, जबकि प्रचलन को दूर किया जा सकता है :)
Rakete1111

1
हम लिख सकते हैं: Creature(const std::string &name) : m_name{std::move(name)} { }में (2) ?
स्काईट्री

4
@skytree: यदि आप स्रोत से परिवर्तन करते हैं, तो आप एक कास्ट ऑब्जेक्ट से नहीं जा सकते। यह संकलन करेगा, लेकिन यह एक प्रतिलिपि बना देगा।
विटोरियो रोमियो

1

आप यहां से कैसे गुजरते हैं यह केवल परिवर्तनशील नहीं है, क्या आप पास करते हैं वह दोनों के बीच बड़ा अंतर बनाता है।

C ++ में, हमारे पास मूल्य श्रेणियों के सभी प्रकार है और इस "मुहावरा" ऐसे मामलों में जहां आप एक में पारित के लिए मौजूद है rvalue (जैसे "Alex-string-literal-that-constructs-temporary-std::string"या std::move(nameString)), जिसमें परिणाम 0 प्रतियां का std::stringबनाया जा रहा है (प्रकार भी नहीं है कॉपी-constructible होने के लिए तर्क के लिए), और केवल उपयोग करता हैstd::string चाल निर्माता ।

कुछ हद तक संबंधित प्रश्नोत्तर


1

पास-दर-मूल्य-और-दृष्टिकोण के कई नुकसान पास-बाय- (आरवी) संदर्भ पर हैं:

  • यह 2 के बजाय 3 वस्तुओं का कारण बनता है;
  • मूल्य द्वारा किसी वस्तु को पास करने से अतिरिक्त स्टैक ओवरहेड हो सकता है, क्योंकि नियमित स्ट्रिंग वर्ग आमतौर पर एक पॉइंटर से कम से कम 3 या 4 गुना बड़ा होता है;
  • तर्क वस्तुओं का निर्माण कॉलर की तरफ किया जा रहा है, जिससे कोड ब्लोट हो सकता है;

क्या आप स्पष्ट कर सकते हैं कि यह 3 वस्तुओं को पैदा करने के लिए क्यों पैदा करेगा? जो मैं समझता हूं कि मैं "पीटर" को एक स्ट्रिंग के रूप में पारित कर सकता हूं। यह spawned हो जाएगा, नकल और फिर चले गए, यह नहीं होगा? और स्टैक को किसी बिंदु पर उपयोग नहीं किया जाएगा? कंस्ट्रक्टर कॉल के बिंदु पर नहीं, बल्कि उस m_name{name}हिस्से में जहां इसकी नकल की जाती है?
ब्लैकबॉट

@ ब्लेकबॉट मैं आपके उदाहरण का उल्लेख कर रहा था कि std::string nameString("Alex"); Creature c(nameString);एक वस्तु हैnameString , दूसरा फ़ंक्शन तर्क है, और तीसरा एक वर्ग फ़ील्ड है।
user7860670 17

0

मेरे मामले में, मान द्वारा पास करने के लिए स्विच करना और फिर एक std करना: इस कदम के कारण एड्रेस सेन्निज़र में एक ढेर-उपयोग-बाद-मुक्त त्रुटि हुई।

https://travis-ci.org/github/acgetchell/CDT-plusplus/jobs/679520360#L3165

तो, मैंने इसे बंद कर दिया है, साथ ही क्लैंग-टिड्डी में सुझाव भी दिया है।

https://github.com/acgetchell/CDT-plusplus/compare/80c96789f0a2...0d78fd63b332

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