2009-04-15 6 views
35

कोड के निम्नलिखित दो टुकड़ों की तुलना करें, पहले एक बड़ी वस्तु के संदर्भ का उपयोग करते हुए, और दूसरे में बड़ी वस्तु वापसी मूल्य के रूप में होती है। "बड़ी वस्तु" पर जोर इस तथ्य को संदर्भित करता है कि वस्तु की दोहराई गई प्रतियां, अनावश्यक रूप से बर्बाद चक्र हैं।कार्यों में बड़े ऑब्जेक्ट्स लौटने

एक बड़ी वस्तु के लिए एक संदर्भ का उपयोग करना:

void getObjData(LargeObj& a) 
{ 
    a.reset() ; 
    a.fillWithData() ; 
} 

int main() 
{ 
    LargeObj a ; 
    getObjData(a) ; 
} 

एक वापसी मान के रूप में बड़ी वस्तु का उपयोग करना:

LargeObj getObjData() 
{ 
    LargeObj a ; 
    a.fillWithData() ; 
    return a ; 
} 

int main() 
{ 
    LargeObj a = getObjData() ; 
} 

कोड के पहले टुकड़ा बड़ी वस्तु को कॉपी आवश्यकता नहीं है।

दूसरे स्निपेट में, ऑब्जेक्ट फ़ंक्शन के अंदर बनाया गया है, और इसलिए सामान्य रूप से, ऑब्जेक्ट लौटने पर प्रतिलिपि की आवश्यकता होती है। इस मामले में, हालांकि, main() में ऑब्जेक्ट घोषित किया जा रहा है। क्या संकलक पहले डिफॉल्ट-निर्मित ऑब्जेक्ट बनाते हैं, फिर getObjData() द्वारा ऑब्जेक्ट की प्रतिलिपि बनाएँ, या यह पहले स्निपेट के रूप में उतना ही कुशल होगा?

मुझे लगता है कि दूसरा स्निपेट पढ़ने में आसान है लेकिन मुझे डर है कि यह कम कुशल है।

संपादित करें: आम तौर पर, मैं सामान्य कंटेनर कक्षाओं के लिए LargeObj मामलों के बारे में सोच रहा हूं, तर्क के लिए, उनमें से हजारों ऑब्जेक्ट्स शामिल हैं। उदाहरण के लिए,

typedef std::vector<HugeObj> LargeObj ; 

तो प्रत्यक्ष रूप से बदलाव/LargeObj करने के तरीकों को जोड़ने के लिए एक सीधा एक्सेस समाधान नहीं है।

उत्तर

40

दूसरा दृष्टिकोण अधिक मूर्खतापूर्ण और अभिव्यक्तिपूर्ण है। कोड को पढ़ते समय यह स्पष्ट होता है कि फ़ंक्शन पर तर्क पर कोई पूर्व शर्त नहीं है (इसमें कोई तर्क नहीं है) और यह वास्तव में अंदर एक वस्तु बना देगा। पहला दृष्टिकोण आकस्मिक पाठक के लिए इतना स्पष्ट नहीं है। कॉल का तात्पर्य है कि वस्तु बदली जाएगी (संदर्भ द्वारा पास) लेकिन यह स्पष्ट नहीं है कि पास ऑब्जेक्ट पर कोई पूर्व शर्त है या नहीं।

प्रतियों के बारे में। आपके द्वारा पोस्ट किया गया कोड असाइनमेंट ऑपरेटर का उपयोग नहीं कर रहा है, बल्कि निर्माण की प्रतिलिपि बना रहा है।सी ++ return value optimization को परिभाषित करता है जो सभी प्रमुख कंपाइलरों में लागू होता है। आप अपने संकलक में निम्नलिखित स्निपेट चला सकते हैं आप सुनिश्चित नहीं हैं:

#include <iostream> 
class X 
{ 
public: 
    X() { std::cout << "X::X()" << std::endl; } 
    X(X const &) { std::cout << "X::X(X const &)" << std::endl; } 
    X& operator=(X const &) { std::cout << "X::operator=(X const &)" << std::endl; } 
}; 
X f() { 
    X tmp; 
    return tmp; 
} 
int main() { 
    X x = f(); 
} 
जी ++ आप एक पंक्ति एक्स :: एक्स() मिल जाएगा के साथ

। संकलक एक्स वस्तु के लिए ढेर में अंतरिक्ष सुरक्षित रखता है, तो अंदर च समारोह है कि tmpसे अधिक एक्स (वास्तव में tmpएक्स है निर्माण करती कहता है। आपरेशन() । आप प्रतिलिपि निर्माता का उपयोग नहीं कर रहे थे, तो एक्स, अपने पहले कोड स्निपेट के बराबर किया जा रहा है पर सीधे लागू होते हैं (संदर्भ द्वारा पारित)

(आप लिखा था: एक्स एक्स, एक्स = च();) तो यह woul घ असाइनमेंट ऑपरेटर दोनों एक्स और tmp बना सकते हैं और लागू होते हैं, एक तीन लाइन उत्पादन उपज: एक्स :: एक्स()/एक्स :: एक्स()/एक्स :: ऑपरेटर =। तो यह मामलों में थोड़ा कम कुशल हो सकता है।

+1

महान स्पष्टीकरण के लिए धन्यवाद, और "परीक्षण" वापसी मूल्य अनुकूलन के लिए कोड। मैं माइक्रोसॉफ्ट विजुअल स्टूडियो 2008 कंपाइलर पर पुष्टि कर सकता हूं जी ++ के समान परिणाम देता है - यानी, संचालन का अनुकूलित सेट है। – swongu

+0

मेरा मानना ​​है कि विजुअल स्टूडियो केवल आरवीओ को/ओ 2 या उच्चतर (जैसे डीबग कोड में नहीं बल्कि केवल ओ/ओ के साथ) सक्षम करेगा। – Bklyn

+5

मैंने इसे VS2008 के साथ चेक किया, और पाया कि/ओडी (अक्षम) एक्स :: एक्स() और एक्स :: एक्स (एक्स कॉन्स एंड) कहता है, जबकि/ओ 1/ओ 2/ऑक्स केवल एक्स :: एक्स() को कॉल करता है। तो ऑप्टिमाइज़ेशन अक्षम होने के साथ, कॉपी f() के बाहर लौटने पर प्रतिलिपि की जाती है। – swongu

3

हां दूसरे मामले में यह ऑब्जेक्ट की प्रतिलिपि बना सकता है, संभवतः दो बार - एक बार फ़ंक्शन से मूल्य वापस करने के लिए, और फिर इसे स्थानीय प्रतिलिपि में मुख्य रूप से असाइन करने के लिए। कुछ कंपाइलर्स दूसरी प्रति को अनुकूलित करेंगे, लेकिन आम तौर पर आप मान सकते हैं कि कम से कम एक प्रति होगी।

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

-1

आपका पहला स्निपेट विशेष रूप से उपयोगी होता है जब आप एक डीएलएल में लागू ओबजडेटा() को प्राप्त करते हैं, इसे किसी अन्य डीएलएल से कॉल करते हैं, और दो डीएलएल एक ही भाषा के लिए विभिन्न भाषाओं या कंपाइलर के विभिन्न संस्करणों में लागू होते हैं। इसका कारण यह है कि जब वे विभिन्न कंपाइलरों में संकलित होते हैं तो वे अक्सर विभिन्न ढेर का उपयोग करते हैं। आपको एक ही ढेर के भीतर से स्मृति आवंटित और आवंटित करना होगा, अन्यथा आप स्मृति को दूषित करेंगे। </windows>

लेकिन आप ऐसा कुछ करना नहीं है, मैं सामान्य रूप से केवल एक सूचक (या स्मार्ट सूचक) स्मृति करने के लिए अपने समारोह वापसी होगी आवंटित:

LargeObj* getObjData() 
{ 
    LargeObj* ret = new LargeObj; 
    ret->fillWithData() ; 
    return ret; 
} 

... जब तक मैं किसी विशेष कारण है नहीं

+0

LOL @ downvote। –

+0

शायद नहीं, किसी कारण से मैंने सोचा कि ओपी ने एक नई स्टैक ऑब्जेक्ट के लिए किया था। –

11

दूसरे दृष्टिकोण का उपयोग करें। ऐसा लगता है कि कम कुशल होना चाहिए, लेकिन सी ++ मानक प्रतियों को छीनने की अनुमति देता है। इस अनुकूलन को Named Return Value Optimization कहा जाता है और अधिकांश मौजूदा कंपाइलरों में लागू किया जाता है।

0

वैकल्पिक रूप से, आप ऑब्जेक्ट को अपना डेटा प्राप्त करके सभी मुद्दों को एक साथ टाल सकते हैं, i। ई। getObjData()LargeObj का सदस्य फ़ंक्शन बनाकर। वास्तव में आप जो कर रहे हैं उसके आधार पर, यह जाने का एक अच्छा तरीका हो सकता है।

+0

मुझे यह उल्लेख करना चाहिए था कि जिन मामलों में मैं सोच रहा हूं, लार्जऑबज किसी प्रकार की मानक कंटेनर क्लास है जिसमें बड़ी संख्या में प्रविष्टियां हैं (जैसे std :: vector <>) और वह कक्षा नहीं है जिसे मैं संशोधित कर सकता हूं। – swongu

+0

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

2

से बचने का तरीका प्रतिलिपि एक विशेष निर्माता प्रदान करना है। अपने कोड आप कर सकते हैं फिर से लिखें तो ऐसा लगता है कि:।

LargeObj getObjData() 
{ 
    return LargeObj(fillsomehow()); 
} 

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

+0

मुझे यह मारो :-) –

0

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

0

संभावना है कि जब आप कॉपी द्वारा वापस आते हैं तो कुछ चक्र बर्बाद हो जाएंगे। चाहे यह चिंता करने योग्य है कि इस बात पर निर्भर करता है कि ऑब्जेक्ट वास्तव में कितना बड़ा है, और आप कितनी बार इस कोड का आह्वान करते हैं।

लेकिन मैं कहना है कि अगर LargeObj एक बड़े और गैर तुच्छ वर्ग है, तो किसी भी मामले में अपने खाली निर्माता एक ज्ञात राज्य के लिए यह आरंभ होना चाहिए चाहते हैं:

LargeObj::LargeObj() : 
m_member1(), 
m_member2(), 
... 
{} 

यह एक बरबाद करती है कुछ चक्र भी। फिर से लिखने के रूप में

LargeObj::LargeObj() 
{ 
    // (The body of fillWithData should ideally be re-written into 
    // the initializer list...) 
    fillWithData() ; 
} 

int main() 
{ 
    LargeObj a ; 
} 

कोड शायद आप के लिए फायदे का सौदा होगा: आप LargeObj उदाहरणों में जाना जाता है और उपयोगी राज्यों में प्रारंभ हो रही है होगा, और आपको कम बर्बाद चक्र होगा।

यदि आप हमेशा निर्माता में fillWithData() का उपयोग नहीं करना चाहते हैं, तो आप निर्माता को एक तर्क के रूप में एक ध्वज पास कर सकते हैं।

अद्यतन (अपने संपादित & टिप्पणी से): शब्दार्थ, अगर यह LargeObj के लिए एक typedef बनाने के लिए फ़ायदेमंद हो सकता है - यानी, यह एक नाम देने के बजाय typedef std::vector<HugeObj> के रूप में बस इसे संदर्भित - तो आप पहले से ही कर रहे हैं इसे अपने स्वयं के व्यवहारिक अर्थशास्त्र देने के लिए सड़क पर। उदाहरण के लिए, आप इसे

class LargeObj : public std::vector<HugeObj> { 
    // constructor that fills the object with data 
    LargeObj() ; 
    // ... other standard methods ... 
}; 

केवल यह निर्धारित कर सकते हैं कि यह आपके ऐप के लिए उपयुक्त है या नहीं। मेरा मुद्दा यह है कि भले ही LargeObj "कंटेनर" है, फिर भी यदि आप अपने आवेदन के लिए ऐसा काम करते हैं तो भी आप कक्षा व्यवहार दे सकते हैं।

+0

मुझे यह उल्लेख करना चाहिए था कि मामलों में मैं इस बारे में सोच रहा हूं, लार्जऑबज किसी प्रकार की मानक कंटेनर कक्षा है जिसमें बड़ी संख्या में प्रविष्टियां हैं (जैसे std :: vector <>) और कुछ नहीं है कि मैं एक कन्स्ट्रक्टर जोड़ सकते हैं। – swongu

+0

यह एक कन्स्ट्रक्टर जोड़ने से इंकार नहीं करता है - मेरा अपडेट देखें। –

+0

सार्वजनिक रूप से एसटीएल कंटेनरों से प्राप्त होने पर आम तौर पर फहराया जाता है। आभासी विनाशकों की कमी एक कारण है, Google आपको और अधिक दे सकता है। – avakar

2

एक कुछ हद तक मुहावरेदार समाधान होगा:

std::auto_ptr<LargeObj> getObjData() 
{ 
    std::auto_ptr<LargeObj> a(new LargeObj); 
    a->fillWithData(); 
    return a; 
} 

int main() 
{ 
    std::auto_ptr<LargeObj> a(getObjData()); 
} 
संबंधित मुद्दे