2015-05-12 6 views
5

एक जटिल codebase में, मैं गैर आभासी आधार वर्ग सूचक (आधार वर्ग कोई आभासी तरीकों है) की एक सरणी हैसी ++ असुरक्षित डाली वैकल्पिक हल

इस कोड पर विचार करें:

#include <iostream> 

using namespace std; 

class TBase 
{ 
    public: 
     TBase(int i = 0) : m_iData(i) {} 
     ~TBase(void) {} 

     void Print(void) {std::cout << "Data = " << m_iData << std::endl;} 

    protected: 
     int  m_iData; 
}; 

class TStaticDerived : public TBase 
{ 
    public: 
     TStaticDerived(void) : TBase(1) {} 
     ~TStaticDerived(void) {} 
}; 

class TVirtualDerived : public TBase 
{ 
    public: 
     TVirtualDerived(void) : TBase(2) {} 
     virtual ~TVirtualDerived(void) {} //will force the creation of a VTABLE 
}; 

void PrintType(TBase *pBase) 
{ 
    pBase->Print(); 
} 

void PrintType(void** pArray, size_t iSize) 
{ 
    for(size_t i = 0; i < iSize; i++) 
    { 
     TBase *pBase = (TBase*) pArray[i]; 
     pBase->Print(); 
    } 
} 


int main() 
{ 
    TBase b(0); 
    TStaticDerived sd; 
    TVirtualDerived vd; 

    PrintType(&b); 
    PrintType(&sd); 
    PrintType(&vd); //OK 

    void* vArray[3]; 
    vArray[0] = &b; 
    vArray[1] = &sd; 
    vArray[2] = &vd; //VTABLE not taken into account -> pointer not OK 
    PrintType(vArray, 3); 

    return 0; 
} 

उत्पादन होता है (Win64 पर Mingw-W64 जीसीसी 4.9.2 के साथ संकलित):

Data = 0 
Data = 1 
Data = 2 
Data = 0 
Data = 1 
Data = 4771632 

विफलता के कारण TVirtualDerived के प्रत्येक उदाहरण वर्चुअल टेबल, जो TBase नहीं है करने के लिए एक सूचक होता है। तो पिछली प्रकार की जानकारी के बिना टीबीएएस को अप-कास्टिंग (शून्य * से टीबीज़ * तक) सुरक्षित नहीं है।

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

प्रसंग:

हम एक संकेत/स्लॉट प्रणाली को लागू कर रहे हैं एक बहुत ही में, बाधित वातावरण (स्मृति गंभीर रूप से सीमित)।

मैं कैसे इस समस्या को हल कर सकते हैं: जब से हम कई लाखों लोगों की वस्तु जो भेजने के लिए या संकेतों को प्राप्त कर सकते हैं, अनुकूलन के इस प्रकार प्रभावी (जब यह निश्चित रूप से, काम करता है)

प्रश्न है? अब तक, मैंने पाया है:

1 - टीबीज़ में वर्चुअल विधि जोड़ें। काम करता है, लेकिन यह वास्तव में समस्या को हल नहीं करता है, यह इससे बचाता है। और यह अक्षम है (बहुत अधिक स्मृति)

2 - सामान्यता के नुकसान की कीमत पर, सरणी में शून्य * को कास्टिंग करने के बजाय टीबीज़ * कास्टिंग। (शायद मैं आगे क्या कोशिश करूंगा)

क्या आप एक और समाधान देखते हैं?

+1

आपकी संतुष्टि के लिए समस्या को सुलझाने के लिए 'शून्य * * पर कास्टिंग करने से पहले' टीबीएस * * * पर कास्टिंग करना होगा? ([यहां देखें] (https : //ideone.com/kpNhe6)) –

+1

बस स्पष्ट होना: आपके कुछ व्युत्पन्न वर्गों में वर्चुअल विधियां हैं। अन्य नहीं करते हैं। और टीबेस खुद इतना छोटा है कि एक vtable सूचक जोड़ने मेमोरी आकार में एक महत्वपूर्ण वृद्धि का कारण बनता है। सही बात? –

+0

क्या आपने टेम्पलेट का उपयोग करने पर विचार किया है? – cup

उत्तर

3

आपको यह समझना होगा कि कक्षा में कक्षा को कैसे रखा गया है। TBase आसान है, यह एक सदस्य के साथ सिर्फ चार बाइट्स है:

_ _ _ _ 
|_|_|_|_| 
^ 
m_iData 

TStaticDerived में ही है। हालांकि, TVirtualDerived पूरी तरह से अलग है। अब यह 8 के एक संरेखण है और एक vtable साथ सामने शुरू करने के लिए, नाशक के लिए एक प्रवेश युक्त है:

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_| 
^    ^
vtable   m_iData 

तो जब आप TBase* को void* को vd डाली और उसके बाद, आप प्रभावी रूप से पहले चार पुनर्व्याख्या कर रहे हैं अपने vtable के बाइट्स (ऑफसेट पता ~TVirtualDerived() में) m_iData के रूप में।

vArray[2] = static_cast<TBase*>(&vd); // now, pointer is OK 
+0

उत्तर के लिए धन्यवाद। मैंने यह बताने के लिए प्रश्न को अद्यतन किया है कि हमें इस प्रणाली का उपयोग करने की आवश्यकता क्यों है। एकाधिक विरासत के मामले में static_cast काम नहीं करेगा। – Seb

+0

@ सेब मैंने आपके संपादन को वापस कर दिया क्योंकि यह टेक्स्ट की दीवार है जो प्रश्न की भावना को सराहनीय रूप से परिवर्तित नहीं करता है। इसके अलावा 'static_cast' निश्चित रूप से एकाधिक विरासत के साथ काम करता है - समस्या यह है कि आप 'शून्य *' पर आगे और आगे कास्टिंग कर रहे हैं। – Barry

4

समस्या आप में डाली है: समाधान पहले एक static_castTBase* है, जो एक सूचक वापस आ जाएगी में vd और तोvoid* को TBase का प्रारंभिक बिंदु को दूर करने के क्या करना है। जैसे ही आप सी 0 प्रकार 3शून्य के माध्यम से उपयोग करते हैं, यह reinterpret_cast के बराबर है, जो सबक्लासिंग के दौरान खराब हो सकता है। पहले भाग में, प्रकार कंपाइलर के लिए सुलभ है और आपके कास्ट static_cast के बराबर हैं।

लेकिन मुझे समझ में नहीं आ रहा है कि आप क्यों कहते हैं कि पहली जगह में रद्द करने से बच नहीं सकता है। चूंकि प्रिंटटाइप आंतरिक रूप से void * को TBase * में परिवर्तित कर देगा, तो आप TBase ** पास कर सकते हैं। , वैकल्पिक रूप से

void PrintType(TBase** pArray, size_t iSize) 
{ 
    for(size_t i = 0; i < iSize; i++) 
    { 
     TBase *pBase = pArray[i]; 
     pBase->Print(); 
    } 
} 
... 
    TBase* vArray[3]; 
    vArray[0] = &b; 
    vArray[1] = &sd; 
    vArray[2] = &vd; //VTABLE not taken into account -> pointer not OK 
    PrintType(vArray, 3); 

यदि आप एक void ** सरणी का उपयोग करने केचाहते हैं, आप स्पष्ट रूप सुनिश्चित करें कि क्या आप इसे में डाल केवल TBase *और नहीं उपवर्गों सूचक हैं बनाना चाहिए: उस मामले में यह ठीक काम करेगा:

void* vArray[3]; 
vArray[0] = &b; 
vArray[1] = static_cast<TBase *>(&sd); 
vArray[2] = static_cast<TBase *>(&vd); 
PrintType(vArray, 3); 

उन दोनों विधि सही ढंग से उत्पादन:

Data = 0 
Data = 1 
Data = 2 
Data = 0 
Data = 1 
Data = 2 
+0

दिया गया कोड समस्या के उदाहरण के लिए था (न्यूनतम उदाहरण)। हमारे कोडबेस (> कोड की 800 000 लाइनों) में, हमारे पास एक टाइपलेस प्रतिनिधि वर्ग है (मुख्य लेख के लिए यह आलेख देखें: [प्रतिनिधि] (http://www.codeproject.com/Articles/7150/Member- फ़ंक्शन- पॉइंटर्स -और-सबसे तेजी से संभव))। ज्यादातर मामलों के लिए टीबीएएस कास्टिंग करना, लेकिन जब एकाधिक विरासत का उपयोग किया जाता है, तो यह अब काम नहीं करता है - हमें मूल सबसे व्युत्पन्न प्रकार की आवश्यकता है, या कंपाइलर विधि पते को सही ढंग से कम करने में सक्षम नहीं होगा। यही कारण है कि हमने ऑब्जेक्ट पॉइंटर को रद्द करने के लिए मूल पॉइंटर को रखने के लिए डाला। – Seb

0

आभासी बहुरूपता भूल जाओ। यह पुराने तरीके से करो।

प्रत्येक टीबीज़ में एक बाइट जोड़ें, प्रिंट विधि में टाइप करें और स्विच स्टेटमेंट को "सही चीज करें" पर इंगित करें। (यह आपको वर्चुअल विधि दृष्टिकोण पर प्रति टीबीज़ प्रति आकार (सूचक) -1 बाइट्स बचाता है।

यदि बाइट जोड़ना अभी भी महंगा है, तो सी/सी ++ बिट फ़ील्ड्स का उपयोग करने पर विचार करें (किसी को भी उनको (ग्रिन) याद रखें) निचोड़ने के लिए फ़ील्ड को किसी अन्य फ़ील्ड में टाइप करें जो उपलब्ध स्थान को भरता नहीं है (उदाहरण के लिए एक हस्ताक्षरित पूर्णांक जिसमें अधिकतम 2^24 - 1 है)

आप कोड बदसूरत, सत्य होंगे, लेकिन आपकी गंभीर स्मृति बाधाएं हैं बदसूरत, भी। काम करता है बदसूरत कोड सुंदर कोड से बेहतर है जो विफल रहता है।

+0

उत्तर के लिए धन्यवाद। हमने इस दृष्टिकोण की कोशिश की है, और यह ज्यादातर वर्गों के लिए काम करता है। लेकिन हमें एक समस्या है जब हम वर्चुअल विधियों के साथ टीबेस और दूसरी कक्षा (दूसरी लाइब्रेरी से) से प्राप्त करना चाहते हैं। यही कारण है कि मैं सवाल पोस्ट करता हूं। हम कुशल कोड करने की कोशिश करते हैं, और यह पहले से ही बहुत बदसूरत है! (लेकिन कुशल!)। बदसूरत के साथ समस्या रखरखाव है: यह महंगा है (अधिक समय लगता है)।और जब हमें 6 महीने बाद कोड को विस्तार/पैच करने की आवश्यकता होती है, तो हमें खेद होगा। – Seb

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