2008-09-03 6 views
30

एक सामान्य नियम के रूप में, मैं सी ++ में पॉइंटर अर्थशास्त्र के बजाय मूल्य का उपयोग करना पसंद करता हूं (यानी का उपयोग vector<Class*> के बजाय)। आमतौर पर प्रदर्शन में मामूली हानि गतिशील रूप से आवंटित वस्तुओं को हटाने के लिए याद रखने के लिए बनाई गई है।क्या मेरे पास सी ++ में मूल्य semantics के साथ polymorphic कंटेनर हो सकता है?

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

#include <iostream> 

using namespace std; 

class Parent 
{ 
    public: 
     Parent() : parent_mem(1) {} 
     virtual void write() { cout << "Parent: " << parent_mem << endl; } 
     int parent_mem; 
}; 

class Child : public Parent 
{ 
    public: 
     Child() : child_mem(2) { parent_mem = 2; } 
     void write() { cout << "Child: " << parent_mem << ", " << child_mem << endl; } 

     int child_mem; 
}; 

int main(int, char**) 
{ 
    // I can have a polymorphic container with pointer semantics 
    vector<Parent*> pointerVec; 

    pointerVec.push_back(new Parent()); 
    pointerVec.push_back(new Child()); 

    pointerVec[0]->write(); 
    pointerVec[1]->write(); 

    // Output: 
    // 
    // Parent: 1 
    // Child: 2, 2 

    // But I can't do it with value semantics 

    vector<Parent> valueVec; 

    valueVec.push_back(Parent()); 
    valueVec.push_back(Child()); // gets turned into a Parent object :(

    valueVec[0].write();  
    valueVec[1].write();  

    // Output: 
    // 
    // Parent: 1 
    // Parent: 2 

} 
मेरा प्रश्न

है: मैं अपने केक (मूल्य अर्थ विज्ञान) है और यह भी खाना (बहुरूपी कंटेनर) कर सकते हैं? या मुझे पॉइंटर्स का उपयोग करना है?

उत्तर

22

चूंकि अलग-अलग वर्गों की वस्तुओं के अलग-अलग आकार होंगे, इसलिए आप स्लाइसिंग समस्या में भाग लेते हैं यदि आप उन्हें मूल्य के रूप में संग्रहीत करते हैं।

एक उचित समाधान कंटेनर सुरक्षित स्मार्ट पॉइंटर्स को स्टोर करना है। मैं आमतौर पर boost :: shared_ptr का उपयोग करता हूं जो एक कंटेनर में स्टोर करने के लिए सुरक्षित है। ध्यान दें कि std :: auto_ptr नहीं है।

vector<shared_ptr<Parent>> vec; 
vec.push_back(shared_ptr<Parent>(new Child())); 

shared_ptr संदर्भ गिनती का उपयोग करता है तो यह अंतर्निहित उदाहरण जब तक सभी संदर्भों को हटा दिया जाता है नहीं हटाया जाएगा।

+3

'boost :: ptr_vector' अक्सर' std :: vector > ' – ben

+4

के लिए एक सस्ता और सरल विकल्प होता है यह उत्तर मूल्य अर्थशास्त्र को संबोधित नहीं करता है। share_ptr टी से व्युत्पन्न कक्षाओं पर संदर्भ अर्थशास्त्र प्रदान करता है, इंस्टेस के लिए, shared_ptr ए, बी; बी .reset (नया व्युत्पन्न 1); ए = बी; Derived1 ऑब्जेक्ट की एक प्रति नहीं बनाता है। – Aaron

+5

मैंने कभी नहीं कहा कि मेरा समाधान मूल्य semantics संबोधित किया। मैंने कहा कि यह "एक उचित समाधान" था। यदि आप पॉलिमॉर्फिक वैल्यू सेमेन्टिक्स रखने के तरीके के बारे में जानते हैं तो सही कदम उठाएं और अपने नोबेल पुरस्कार को इकट्ठा करें। –

2

reinterpret_cast
में सी ++ प्रोग्रामिंग भाषा, 3 एड, Bjarne Stroustrup पेज 130. पर यह वर्णन वहाँ अध्याय में इस पर एक पूरा खंड है 6.
आप फिर से पात्र चयन कर सकते हैं static_cast और पर एक नजर डालें बाल वर्ग के लिए आपका अभिभावक वर्ग। यह आपको यह जानने की आवश्यकता है कि प्रत्येक व्यक्ति कब होता है। पुस्तक में, डॉ स्ट्रास्ट्रप इस स्थिति से बचने के लिए विभिन्न तकनीकों के बारे में बात करते हैं।

ऐसा मत करें। यह उस बहुरूपता को अस्वीकार करता है जिसे आप पहली जगह हासिल करने की कोशिश कर रहे हैं!

3

आप boost::any पर भी विचार कर सकते हैं। मैंने इसे विषम कंटेनर के लिए इस्तेमाल किया है। मूल्य वापस पढ़ने पर, आपको कोई भी_कास्ट करने की आवश्यकता है। यदि यह विफल रहता है तो यह bad_any_cast फेंक देगा। यदि ऐसा होता है, तो आप पकड़ सकते हैं और अगले प्रकार पर जा सकते हैं।

I मानते हैं यदि आप किसी व्युत्पन्न कक्षा को किसी भी आधार पर किसी भी श्रेणी का उपयोग करने का प्रयास करते हैं तो यह एक bad_any_cast फेंक देगा। मैं इसे करने की कोशिश:

// But you sort of can do it with boost::any. 

    vector<any> valueVec; 

    valueVec.push_back(any(Parent())); 
    valueVec.push_back(any(Child()));  // remains a Child, wrapped in an Any. 

    Parent p = any_cast<Parent>(valueVec[0]); 
    Child c = any_cast<Child>(valueVec[1]); 
    p.write(); 
    c.write(); 

    // Output: 
    // 
    // Parent: 1 
    // Child: 2, 2 

    // Now try casting the child as a parent. 
    try { 
     Parent p2 = any_cast<Parent>(valueVec[1]); 
     p2.write(); 
    } 
    catch (const boost::bad_any_cast &e) 
    { 
     cout << e.what() << endl; 
    } 

    // Output: 
    // boost::bad_any_cast: failed conversion using boost::any_cast 

सभी ही कहा जा रहा है, मैं भी shared_ptr मार्ग पहले जाना होगा! बस सोचा कि यह कुछ हित में हो सकता है।

3

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

तो क्या करना है?वैसे कुछ प्यारे विकल्प हैं, लेकिन अधिकांश कुछ विषयों या संयोजनों में से एक पर वेरिएंट को कम कर देंगे: एक उपयुक्त स्मार्ट पॉइंटर चुनना या आविष्कार करना, कुछ चालाक तरीके से टेम्पलेट्स या टेम्पलेट टेम्पलेट्स के साथ खेलना, कंटेनरों के लिए एक आम इंटरफ़ेस का उपयोग करना जो प्रति-containee डबल-प्रेषण को लागू करने के लिए एक हुक प्रदान करता है।

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

मैंने पहले, वर्चुअल प्रॉक्सी/लिफाफा-पत्र/संदर्भित गिनती पॉइंटर्स के साथ उस सुंदर चाल को सी ++ में मूल्य अर्थपूर्ण प्रोग्रामिंग के आधार के आधार पर कुछ करने के लिए कल्पना करने का प्रयास करने में कुछ समय बिताया है।

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

2

सभी 1800 INFORMATION पर एक चीज़ जोड़ने के लिए पहले से ही कहा गया है।

आप इस मुद्दे को बेहतर ढंग से समझने के लिए स्कॉट मेयर्स द्वारा "More Effective C++" पर एक नज़र डालना चाहेंगे "आइटम 3: कभी भी एरेज़ पॉलिमॉर्फिक का इलाज न करें"।

10

हाँ, आप कर सकते हैं।

boost.ptr_container लाइब्रेरी मानक कंटेनर के पॉलीमोर्फिक वैल्यू अर्थात् संस्करण प्रदान करता है। आपको केवल एक हीप-आवंटित ऑब्जेक्ट में पॉइंटर में पास करना होगा, और कंटेनर स्वामित्व लेगा और सभी आगे के ऑपरेशन वैल्यू सेमेन्टिक्स प्रदान करेंगे, स्वामित्व को पुनः प्राप्त करने के अलावा, जो आपको स्मार्ट पॉइंटर का उपयोग करके मूल्य semantics के लगभग सभी लाभ देता है ।

10

मैं बस यह इंगित करना चाहता था कि वेक्टर <Foo> आमतौर पर वेक्टर < Foo * > से अधिक कुशल है। एक वेक्टर < फू > में, सभी फ़ूज़ स्मृति में एक-दूसरे के समीप होंगे। एक ठंडा टीएलबी और कैश मानते हुए, पहला पठन पृष्ठ को टीएलबी में जोड़ देगा और वेक्टर के एक हिस्से को एल # कैश में खींच देगा; बाद के पठन गर्म कैश और लोड टीएलबी का उपयोग करेंगे, कभी-कभी कैश मिस और कम बार-बार टीएलबी दोषों के साथ।

एक वेक्टर < फू * > के साथ इसकी तुलना करें: जब आप वेक्टर भरते हैं, तो आप अपनी स्मृति आवंटक से Foo * प्राप्त करते हैं। मान लें कि आपका आवंटन बेहद स्मार्ट नहीं है, (tcmalloc?) या आप समय के साथ धीरे-धीरे वेक्टर भरते हैं, प्रत्येक फू का स्थान दूसरे फूज़ से बहुत दूर होने की संभावना है: शायद सैकड़ों बाइट्स से, शायद मेगाबाइट अलग हो।

सबसे खराब स्थिति में, आप एक वेक्टर < फू * > और प्रत्येक सूचक आप एक TLB गलती और कैश मिस देना पड़ेगा अपसंदर्भन के माध्यम से स्कैन के रूप में - यह एक बहुत यदि आप एक वेक्टर था की तुलना में धीमी जा रहा है खत्म हो जाएगा < फू >। (ठीक है, वास्तव में सबसे बुरे मामले में, प्रत्येक फू को डिस्क पर बाहर निकाल दिया गया है, और प्रत्येक पठन पृष्ठ को वापस ले जाने के लिए डिस्क() को पढ़ता है और पढ़ता है() पृष्ठ को वापस रैम में ले जाने के लिए।)

तो, जब भी उचित हो वेक्टर <Foo> का उपयोग जारी रखें। :-)

+2

+1! भविष्य में यह समस्या अधिक प्रासंगिक हो जाएगी। –

+2

यह इस बात पर निर्भर करता है कि Foo :: Foo (Const Foo &) कितना महंगा है, क्योंकि मूल्य semantics कंटेनर को इसे आवेषण पर कॉल करने की आवश्यकता होगी। – AndrewR

+0

अच्छा बिंदु - हालांकि जब तक आप संलग्न कर रहे हैं, यह कोई मुद्दा नहीं है। (आपके पास लॉग 2 (एन) अतिरिक्त प्रतियां होंगी।) इसके अलावा, अधिकतर एक्सेस पढ़ी जाती हैं और लिखती नहीं हैं; कभी-कभी कभी-कभार महंगे लेखन को पीड़ित करना अभी भी तेजी से पढ़कर शुद्ध जीत हो सकता है। – 0124816

1

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

यह एक विचार है जो आपकी आवश्यकताओं के अनुरूप हो सकता है।

संबंधित मुद्दे

 संबंधित मुद्दे