C ++ में "ऑब्जेक्ट वापस कैसे करें"?


167

मुझे पता है कि शीर्षक कुछ परिचित हैं जैसे कि कई समान प्रश्न हैं, लेकिन मैं समस्या के एक अलग पहलू के लिए कह रहा हूं (मुझे पता है कि स्टैक पर चीजें होने और उन्हें ढेर पर रखने के बीच का अंतर पता है)।

जावा में मैं हमेशा "स्थानीय" वस्तुओं के संदर्भ वापस कर सकता हूं

public Thing calculateThing() {
    Thing thing = new Thing();
    // do calculations and modify thing
    return thing;
}

C ++ में, समान कार्य करने के लिए मेरे पास 2 विकल्प हैं

(1) जब भी मुझे किसी वस्तु को "वापस" करने की आवश्यकता हो तो मैं संदर्भों का उपयोग कर सकता हूं

void calculateThing(Thing& thing) {
    // do calculations and modify thing
}

फिर इसे इस तरह इस्तेमाल करें

Thing thing;
calculateThing(thing);

(2) या मैं एक डायनामिक रूप से आवंटित ऑब्जेक्ट के लिए एक पॉइंटर वापस कर सकता हूं

Thing* calculateThing() {
    Thing* thing(new Thing());
    // do calculations and modify thing
    return thing;
}

फिर इसे इस तरह इस्तेमाल करें

Thing* thing = calculateThing();
delete thing;

पहले दृष्टिकोण का उपयोग करके मुझे मेमोरी को मैन्युअल रूप से मुक्त नहीं करना होगा, लेकिन मेरे लिए यह कोड को पढ़ना मुश्किल बनाता है। दूसरे दृष्टिकोण के साथ समस्या है, मुझे याद रखना होगा delete thing;, जो कि बहुत अच्छा नहीं लगता है। मैं एक कॉपी किया गया मान वापस नहीं करना चाहता क्योंकि यह अक्षम है (मुझे लगता है), इसलिए यहां प्रश्न आते हैं

  • क्या कोई तीसरा समाधान है (जिसमें मूल्य की प्रतिलिपि बनाने की आवश्यकता नहीं है)?
  • क्या कोई समस्या है अगर मैं पहले समाधान के लिए छड़ी?
  • मुझे दूसरे समाधान का उपयोग कब और क्यों करना चाहिए?

32
सवाल डालने के लिए +1।
कंगण

1
बहुत पांडित्यपूर्ण होने के लिए, यह कहना थोड़ा कठिन है कि "कार्य कुछ लौटाते हैं"। अधिक सही ढंग से, फ़ंक्शन कॉल का मूल्यांकन करना एक मूल्य पैदा करता है । मूल्य हमेशा एक वस्तु है (जब तक कि यह एक शून्य कार्य नहीं है)। अंतर यह है कि क्या मूल्य एक चमक या एक प्रचलन है - जो यह निर्धारित करता है कि क्या घोषित रिटर्न प्रकार एक संदर्भ है या नहीं।
केरेक एसबी

जवाबों:


107

मैं एक कॉपी किया गया मान वापस नहीं करना चाहता क्योंकि यह अक्षम है

इसे साबित करो।

आरवीओ और एनआरवीओ, और सी ++ 0x चाल-शब्दार्थ में देखें। C ++ 03 में अधिकांश मामलों में, एक आउट पैरामीटर आपके कोड को बदसूरत बनाने का एक अच्छा तरीका है, और C ++ 0x में आप वास्तव में आउट पैरामीटर का उपयोग करके खुद को चोट पहुँचा रहे हैं।

बस साफ कोड लिखें, मान से लौटें। यदि प्रदर्शन एक समस्या है, तो इसे प्रोफ़ाइल करें (अनुमान लगाना बंद करें), और इसे ठीक करने के लिए आप क्या कर सकते हैं। यह संभावना नहीं है कि कार्यों से वापस लौट रहे हैं।


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

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


10
@phunehehe: कोई भी बिंदु अटकल नहीं लगा रहा है, आपको अपना कोड प्रोफाइल करना चाहिए और पता लगाना चाहिए। (संकेत: नहीं।) कंपाइलर बहुत स्मार्ट हैं, वे चीजों को कॉपी करने में समय बर्बाद नहीं करने वाले हैं यदि उन्हें नहीं करना है। यहां तक ​​कि अगर कुछ लागत की नकल करते हैं, तो आपको अभी भी तेज कोड पर अच्छे कोड के लिए प्रयास करना चाहिए; जब गति एक समस्या बन जाती है तो अच्छा कोड अनुकूलित करना आसान होता है। आपके विचार में कोई समस्या नहीं है, कुछ के लिए कोड को रोकने का कोई मतलब नहीं है; खासकर यदि आप वास्तव में इसे धीमा करते हैं या इससे कुछ भी नहीं निकालते हैं। और अगर आप C ++ 0x का उपयोग कर रहे हैं, तो चाल-शब्दार्थ इसे एक गैर-मुद्दा बनाते हैं।
GManNickG

1
@ मेन, फिर से: आरवीओ: वास्तव में यह केवल तभी सच है जब आपका कॉलर और कैली एक ही संकलन इकाई में हों, जो कि वास्तविक दुनिया में ऐसा नहीं है। तो, आप निराशा में हैं यदि आपका कोड सभी को समाप्त नहीं किया गया है (जिस स्थिति में यह सभी एक संकलन इकाई में होगा) या आपके पास कुछ लिंक-टाइम ऑप्टिमाइज़ेशन है (जीसीसी केवल 4.5 से है)।
एलेक्स बी

2
@ एलेक्स: अनुवाद इकाइयों में अनुकूलन के लिए कंपाइलर बेहतर और बेहतर हो रहे हैं। (वीसी अब इसे कई रिलीज के लिए करता है।)
sbi

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

6
@ घर, जाँच करने पर यह सही प्रतीत होता है! मैं अपना स्पष्ट गलत बयान वापस लेता हूं।
एलेक्स बी

41

बस वस्तु बनाएं और उसे वापस करें

Thing calculateThing() {
    Thing thing;
    // do calculations and modify thing
     return thing;
}

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


2
Thing thing();एक स्थानीय समारोह घोषित करता है और वापस लौटता है Thing
ड्रीमलैक्स

2
बात बात () एक समारोह एक बात वापस घोषित करता है। आपके फ़ंक्शन बॉडी में कोई थिंग ऑब्जेक्ट नहीं बनाया गया है।
सीबी बेली

@dreamlax @Charles @GMan थोड़ी देर, लेकिन तय हो गई।
अमीर रचम

यह C ++ 98 में कैसे काम करता है? मुझे CINT दुभाषिया पर त्रुटियां मिलती हैं और सोच रहा था कि यह C ++ 98 या CINT के कारण ही है ...!
xcorat

16

बस इस तरह एक वस्तु वापस:

Thing calculateThing() 
{
   Thing thing();
   // do calculations and modify thing
   return thing;
}

यह कॉपी कंस्ट्रक्टर को थिंग्स पर आमंत्रित करेगा, ताकि आप अपना खुद का कार्यान्वयन करना चाहें। ऐशे ही:

Thing(const Thing& aThing) {}

यह थोड़ा धीमा हो सकता है, लेकिन यह एक मुद्दा नहीं हो सकता है।

अपडेट करें

कंपाइलर शायद कॉपी कंस्ट्रक्टर को कॉल का अनुकूलन करेगा, इसलिए कोई अतिरिक्त ओवरहेड नहीं होगा। (जैसा कि स्वप्निलक्स ने टिप्पणी में बताया है)।


9
Thing thing();एक स्थानीय फ़ंक्शन को लौटाने की घोषणा करता है Thing, साथ ही, मानक कंपाइलर को आपके द्वारा प्रस्तुत किए गए मामले में कॉपी कंस्ट्रक्टर को छोड़ने की अनुमति देता है; किसी भी आधुनिक संकलक शायद यह करेंगे।
ड्रीमलैक्स

1
आप कॉपी कंस्ट्रक्टर को लागू करने के लिए एक अच्छा बिंदु लाते हैं, खासकर अगर एक गहरी कॉपी की आवश्यकता होती है।
मबदावी २३

+1 के बारे में स्पष्ट रूप से कॉपी कंस्ट्रक्टर के बारे में बताते हुए, हालांकि @dreamlax का कहना है कि कंपाइलर कॉपी कंस्ट्रक्टर को वास्तव में आवश्यक कॉल से बचने वाले कार्यों के लिए रिटर्निंग कोड को संभवतः "अनुकूलित" करेगा।
jose.angel.jimenez

2018 में, वीएस 2017 में, यह कदम निर्माणकर्ता का उपयोग करने की कोशिश कर रहा है। यदि मूव कंस्ट्रक्टर को हटा दिया गया है और कॉपी कंस्ट्रक्टर नहीं है, तो वह संकलन नहीं करेगा।
एंड्रयू

11

क्या आपने स्मार्ट पॉइंटर्स (यदि थिंग वास्तव में बड़ी और भारी वस्तु है) का उपयोग करने की कोशिश की, जैसे auto_ptr:


std::auto_ptr<Thing> calculateThing()
{
  std::auto_ptr<Thing> thing(new Thing);
  // .. some calculations
  return thing;
}


// ...
{
  std::auto_ptr<Thing> thing = calculateThing();
  // working with thing

  // auto_ptr frees thing 
}

4
auto_ptrs पदावनत हैं; उपयोग shared_ptrया unique_ptrइसके बजाय।
एमबीराडले

बस इसे यहाँ जोड़ने जा रहा हूँ ... मैं वर्षों से c ++ का उपयोग कर रहा हूँ, यद्यपि c ++ के साथ पेशेवर रूप से नहीं ... मैंने अब स्मार्ट पॉइंटर्स का उपयोग नहीं करने का प्रयास किया है, वे सिर्फ एक पूर्ण गड़बड़ imo हैं और सभी का कारण बनते हैं समस्याओं की तरह, वास्तव में बहुत तेजी से कोड को गति देने में मदद नहीं। मैं इतना ही बल्कि बस आसपास डेटा कॉपी और संकेत का प्रबंधन, RAII का उपयोग कर रहा था। इसलिए मैं सलाह देता हूं कि यदि आप कर सकते हैं, तो स्मार्ट पॉइंटर्स से बचें।
एंड्रयू

8

यह निर्धारित करने का एक त्वरित तरीका है कि एक कॉपी कंस्ट्रक्टर को बुलाया जा रहा है, अपने क्लास के कॉपी कंस्ट्रक्टर में लॉगिंग जोड़ना है:

MyClass::MyClass(const MyClass &other)
{
    std::cout << "Copy constructor was called" << std::endl;
}

MyClass someFunction()
{
    MyClass dummy;
    return dummy;
}

पुकार someFunction; "कॉपी कंस्ट्रक्टर को कॉपी कहा जाता है" की संख्या जो आपको मिलेगी, वह 0, 1 और 2 के बीच अलग-अलग होगी। यदि आपको कोई नहीं मिलता है, तो आपके कंपाइलर ने रिटर्न वैल्यू को ऑप्टिमाइज़ कर दिया है (जो इसे करने की अनुमति है)। यदि आपको 0 नहीं मिलता है, और आपका कॉपी कंस्ट्रक्टर हास्यास्पद रूप से महंगा है, तो अपने कार्यों से उदाहरणों को वापस करने के वैकल्पिक तरीकों की तलाश करें।


1

सबसे पहले आपके पास कोड में एक त्रुटि है, आप मतलब है Thing *thing(new Thing());, और केवल return thing;

  • का उपयोग करें shared_ptr<Thing>। थेरेप के रूप में इसे एक सूचक था। आपके लिए यह हटा दिया जाएगा जब Thingनिहित का अंतिम संदर्भ कार्यक्षेत्र से बाहर हो जाएगा।
  • भोली पुस्तकालयों में पहला समाधान बहुत आम है। इसका कुछ प्रदर्शन है, और वाक्य-रचना ओवरहेड है, यदि संभव हो तो इसे टाल दें
  • दूसरे समाधान का उपयोग केवल तभी करें जब आप गारंटी दे सकते हैं कि कोई अपवाद नहीं फेंका जाएगा, या जब प्रदर्शन बिल्कुल महत्वपूर्ण होगा (आप प्रासंगिक होने से पहले सी या असेंबली के साथ हस्तक्षेप करेंगे)।

0

मुझे यकीन है कि एक C ++ विशेषज्ञ एक बेहतर जवाब के साथ आएगा, लेकिन व्यक्तिगत रूप से मुझे दूसरा दृष्टिकोण पसंद है। स्मार्ट पॉइंटर्स का उपयोग करने से आपको भूलने की समस्या से निपटने में मदद मिलती है deleteऔर जैसा कि आप कहते हैं, यह हाथ से पहले एक ऑब्जेक्ट बनाने की तुलना में क्लीनर दिखता है (और यदि आप इसे ढेर पर आवंटित करना चाहते हैं तो भी इसे हटाना होगा)।

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