2012-04-12 15 views
6

यह प्रश्न सी ++ भाषा के बारे में नहीं है (यानी मानक के बारे में नहीं) लेकिन वर्चुअल फ़ंक्शन के लिए वैकल्पिक योजनाओं को लागू करने के लिए एक कंपाइलर को कैसे कॉल करें।vptr को लागू करने के लिए वैकल्पिक योजनाएं?

आभासी कार्यों को लागू करने के लिए सामान्य योजना पॉइंटर्स की एक तालिका में सूचक का उपयोग कर रही है।

class Base { 
    private: 
     int m; 
    public: 
     virtual metha(); 
}; 

समतुल्य रूप कहते हैं में सी होगा की तरह

struct Base { 
    void (**vtable)(); 
    int m; 
} 

पहले सदस्य कुछ आमतौर पर आदि (स्मृति जो आवेदन किया है में क्षेत्र का एक टुकड़ा आभासी कार्यों की एक सूची, के लिए सूचक है का कोई नियंत्रण नहीं)। और ज्यादातर मामलों में सदस्यों पर विचार करने से पहले सूचक के आकार की लागत होती है। इसलिए 32 बिट एड्रेसिंग स्कीम में लगभग 4 बाइट्स इत्यादि। यदि आपने अपने अनुप्रयोगों में 40k पॉलीमोर्फिक ऑब्जेक्ट्स की एक सूची बनाई है, तो यह लगभग 40k x है किसी भी सदस्य चर, आदि से पहले 4 बाइट = 160k बाइट्स। मुझे यह भी पता है कि यह सी ++ संकलन के बीच सबसे तेज़ और सामान्य कार्यान्वयन होता है।

मुझे पता है कि यह एकाधिक विरासत से जटिल है (विशेष रूप से उनमें वर्चुअल कक्षाओं, यानी हीरे संरचना, आदि के साथ)।

भी ऐसा ही करने का एक वैकल्पिक तरीका vptrs (नीचे के रूप में सी में समतुल्य रूप)

struct Base { 
    char classid;  // the classid here is an index into an array of vtables 
    int  m; 
} 

की एक तालिका में एक सूचकांक आईडी के रूप में पहली चर के लिए एक आवेदन पत्र में कक्षाओं की कुल संख्या है, तो है 255 से कम (सभी संभावित टेम्पलेट इंस्टॉलेशन, इत्यादि सहित), फिर एक चार्ट इंडेक्स को पकड़ने के लिए पर्याप्त है जिससे आवेदन में सभी पॉलिमॉर्फिक वर्गों के आकार को कम किया जा सके (मैं संरेखण के मुद्दों को छोड़ रहा हूं, आदि)।

मेरे प्रश्न है, क्या ऐसा करने के लिए जीएनयू सी ++, एलएलवीएम, या किसी अन्य कंपाइलर में कोई स्विच है ?? या polymorphic वस्तुओं के आकार को कम करें?

संपादित करें: मैं संरेखण के मुद्दों के बारे में समझता हूं। इसके अलावा, एक और बिंदु, अगर यह 64 बिट सिस्टम (64 बिट वीपीटीआर मानते हुए) पर था, तो प्रत्येक पॉलीमोर्फिक ऑब्जेक्ट सदस्यों के पास 8 बाइट्स की लागत होती है, तो वीपीटीआर की लागत स्मृति का 50% है। यह ज्यादातर द्रव्यमान में बनाए गए छोटे बहुलक से संबंधित है, इसलिए मैं सोच रहा हूं कि यदि यह योजना कम से कम विशिष्ट वर्चुअल ऑब्जेक्ट्स के लिए संभव है तो पूरे एप्लिकेशन को नहीं।

+11

सदस्य मीटर शायद अभी भी एक DWORD सीमा पर गठबंधन किया जाएगा, ताकि आप कुछ भी नहीं हासिल होगा। – Henrik

+1

उस योजना के साथ मुद्दा शायद संरेखण में से एक होगा। यहां आपके दूसरे उदाहरण में स्ट्रक्चर बेस (आमतौर पर) 5 बाइट्स नहीं लेगा, जिससे आप प्रति ऑब्जेक्ट 3 बाइट्स बचा सकते हैं। एम को किसी भी तरह से 4 बाइट सीमा पर गठबंधन किया जाएगा, इसलिए आपके पास बर्बाद अंतरिक्ष के 3 बाइट होंगे –

+1

मुझे लगता है कि आभासी तालिका कार्यान्वयन की यह विधि लगभग सार्वभौमिक है। मैं निश्चित रूप से कभी भी किसी अन्य विधि, या जीसीसी या विजुअल सी ++ पर किसी भी विकल्प पर नहीं आया हूं जो इसे नियंत्रित करेगा कि इसे कैसे कार्यान्वित किया गया था। एक दिलचस्प प्रश्न –

उत्तर

2

आप सुझाव दिलचस्प हैं, लेकिन यह निष्पादन योग्य नहीं होगा यदि निष्पादन योग्य कई मॉड्यूल से बना है, उनके बीच वस्तुओं को पारित कर रहा है। यह देखते हुए कि वे अलग-अलग संकलित होते हैं (डीएलएल कहते हैं), यदि एक मॉड्यूल ऑब्जेक्ट बनाता है और इसे दूसरे पर भेजता है, और दूसरा वर्चुअल विधि का आह्वान करता है - यह कैसे पता चलेगा कि classid किस तालिका को संदर्भित करता है? आप एक और moduleid जोड़ने में सक्षम नहीं होंगे क्योंकि दो मॉड्यूल संकलित होने पर एक दूसरे के बारे में नहीं जानते हैं। तो जब तक आप संकेत का उपयोग करें, मुझे लगता है कि यह एक मृत अंत है ...

+0

सहमत हैं लेकिन मुझे लगता है कि यह आंशिक रूप से टूलचेन, पर्यावरण (गतिशील, आदि), ओएस पर होगा। – ManiP

+0

@ManiP, टूलचेन शायद नहीं जानता कि अन्य मॉड्यूल कैसे बनाए गए थे और उनमें क्या शामिल है (यह नियमित vptrs के साथ भी समस्या हो सकती है, जो हमेशा एक ही स्थान पर नहीं रखी जाती हैं)। ध्यान रखें कि vptrs तालिका के सूचक को संकलन समय पर जाना जाना चाहिए। रनटाइम पर सही तालिका निर्धारित करने के लिए आपको ऑब्जेक्ट की उत्पत्ति (जिसे मॉड्यूल ने बनाया है) को जानना होगा, और यह एक गंभीर ओवरहेड है (आप उस डेटा को संकलन समय पर ऑब्जेक्ट में संग्रहीत नहीं कर सकते हैं)। तो अगर आप किसी भी तरह से उस vptr को (भाग) का उन्मूलन करते हैं, तो आप अधिक कोड चलाएंगे। – eran

+0

अच्छा बिंदु! लिनक्स की तरह आप क्रॉस फाइल-सिस्टम हार्ड लिंक फ़ाइल नहीं बना सकते हैं। एक ही समस्या इनोड संख्या और विभाजन के साथ जाती है। –

2

टिप्पणियों के एक जोड़े:

  1. हाँ, एक छोटे मूल्य वर्ग का प्रतिनिधित्व करने के लिए इस्तेमाल किया जा सकता है, लेकिन कुछ प्रोसेसर चाहते हैं कि डेटा गठबंधन किया जाए ताकि अंतरिक्ष में बचत डेटा मानों को संरेखित करने की आवश्यकता से खो जा सके 4 बाइट सीमाएं। इसके अलावा, कक्षा-आईडी एक पॉलिमॉर्फिक विरासत वृक्ष के सभी सदस्यों के लिए एक अच्छी तरह से परिभाषित जगह में होना चाहिए, इसलिए यह दूसरी तारीख से आगे होने की संभावना है, इसलिए संरेखण समस्याओं से बचा जा सकता है।

  2. सूचक भंडारण की लागत कोड है, जहां एक बहुरूपी समारोह के हर उपयोग या तो एक vtable सूचक, या कुछ बराबर डेटा संरचना करने के लिए वर्ग-आईडी अनुवाद करने के लिए कोड की आवश्यकता है के लिए ले जाया गया है। तो यह मुफ्त में नहीं है। स्पष्ट रूप से लागत व्यापार-बंद वस्तुओं की संख्या बनाम संख्या की मात्रा पर निर्भर करता है।

  3. यदि ऑब्जेक्ट्स को ढेर से आवंटित किया जाता है, तो आमतौर पर वस्तुओं को सबसे खराब सीमा से संगत करने के लिए ऑरर में जगह बर्बाद होती है, इसलिए यदि कोड की थोड़ी मात्रा होती है, और बड़ी संख्या में पॉलीमोर्फिक ऑब्जेक्ट्स, मेमोरी मैनेजमेंट ओवरहेड माई पॉइंटर और चार के बीच के अंतर से काफी बड़ा है।

  4. कार्यक्रमों को स्वतंत्र रूप से संकलित करने की अनुमति देने के लिए, पूरे कार्यक्रम में कक्षाओं की संख्या, और इसलिए कक्षा-आईडी का आकार संकलन समय पर जाना जाना चाहिए, अन्यथा कोड को एक्सेस करने के लिए संकलित नहीं किया जा सकता है । यह एक महत्वपूर्ण ओवरहेड होगा। सबसे खराब मामले के लिए इसे ठीक करना आसान है, और संकलन और लिंकिंग को सरल बनाना।

कृपया मुझे आप का प्रयास करना रोक देना नहीं है, लेकिन क्या कोई तकनीक है जो समारोह का पता प्राप्त करने के लिए एक चर आकार आईडी का उपयोग कर सकते हैं का उपयोग कर हल करने के लिए काफी एक बहुत अधिक मुद्दे हैं।

मैं दृढ़ता से Wikipedia Cola

पर Ian Piumarta's Cola भी को देखने के लिए प्रोत्साहित करते हैं यह वास्तव में एक अलग दृष्टिकोण लेता है, और एक बहुत अधिक लचीला तरीका में सूचक का उपयोग करता है, विरासत, या प्रोटोटाइप के आधार पर, या किसी का निर्माण करने के लिए डेवलपर की अन्य तंत्र की आवश्यकता है।

+0

बिंदु 2 के संबंध में: वर्चुअल कॉल की लागत एक कूद तालिका (फ़ंक्शन पॉइंटर्स के माध्यम से) के कॉल के समान होती है। बिंदु 3 के बारे में: उपयोगी, लेकिन ध्यान से पैक की गई वस्तुएं इस मुद्दे से बचती हैं। बिंदु 4 के बारे में: सबसे खराब, 32 बिट्स पर्याप्त होना चाहिए। 64-बिट्स प्लेटफार्म पर यह प्रति वस्तु 4 बाइट्स अर्थव्यवस्था संभव है। कोला के लिंक के लिए धन्यवाद :) –

+0

बिंदु 2 के बारे में: सहमत है कि यह मुफ़्त नहीं है और एक अतिरिक्त लागत है। यह आमतौर पर 'क्लासिड एक्स 4 + [बेस पीटीआर से vtables]' की तरह कुछ है। मुझे लगता है कि आधुनिक प्रोसेसर अपेक्षाकृत तेजी से लागू होना चाहिए। – ManiP

+0

@ManiP - हां। लागत एक अतिरिक्त निर्देश या दो है, संभावित रूप से कोड में हर जगह पर जो एक विधि कहता है (स्पष्ट रूप से कुछ अनुकूलन संभव है)। बनाता – gbulmer

2

संक्षिप्त उत्तर यह है कि नहीं, मुझे किसी भी सामान्य सी ++ कंपाइलर के साथ ऐसा करने के लिए किसी भी स्विच के बारे में पता नहीं है।

अब जवाब है कि ऐसा करने के लिए, आप बस के बारे में लिंकर में खुफिया के सबसे निर्माण करने के लिए होगा है, इसलिए इसे भर में सभी वस्तु फ़ाइलों को एक साथ जोड़ा जा रहा आईडी वितरण समन्वय कर सकते हैं।

मैं यह भी इंगित करता हूं कि यह आमतौर पर बहुत अच्छा नहीं होगा। कम से कम एक सामान्य मामले में, आप प्रत्येक तत्व को एक "प्राकृतिक" सीमा पर संरचना/कक्षा में चाहते हैं, जिसका अर्थ है कि इसका प्रारंभिक पता इसके आकार का एक बहु है। एक एकल int युक्त कक्षा के अपने उदाहरण का उपयोग करके, कंपाइलर vtable सूचकांक के लिए एक बाइट आवंटित करेगा, तुरंत पैडिंग के तीन बाई द्वारा पीछा किया जाएगा ताकि अगले int एक पते पर उतरे जो चार में से एक था। अंत परिणाम यह होगा कि कक्षा की वस्तुओं ठीक पर समान मात्रा में भंडारण पर कब्जा करेगी जैसे कि हम एक सूचक का उपयोग करते हैं।

मैं जोड़ता हूं कि यह एक दूर-दराज वाला अपवाद नहीं है। सालों से, structs/वर्गों में डाले गए पैडिंग को कम करने के लिए मानक सलाह शुरुआती दिनों में वस्तुओं को सबसे बड़ी होने की उम्मीद है, और सबसे छोटी की तरफ बढ़ती है। इसका अर्थ है अधिकांश कोड में, आप स्ट्रक्चर के पहले स्पष्ट रूप से परिभाषित सदस्य से पहले पैडिंग के उन तीन बाइट्स के साथ समाप्त हो जाएंगे।

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

संक्षेप में: यह लागू नहीं किया गया है, और यदि यह आमतौर पर अधिक नहीं होता है।

+0

* अंतिम परिणाम यह होगा कि वर्ग की वस्तुओं के भंडारण के ठीक एक ही राशि पर कब्जा के रूप में अगर हम एक सूचक का इस्तेमाल किया। * 32-बिट बनाता है केवल के लिए, 64-बिट के लिए एक सूचक 8 बाइट्स, आम तौर पर दो बार आकार है एक पूर्णांक का। लेकिन निश्चित रूप से कक्षा में किसी भी अन्य सूचक एक ही मुद्दे का कारण बन जाएगा। –

+0

मैं संरेखण मुद्दे के बारे में समझता हूं और सहमत हूं। हां अधिकांश कंपाइलर 32 बिट भरने के लिए उन अतिरिक्त 3 वर्णों को पैड करेंगे। 64 बिट मामलों में ओवरहेड डबल लगता है। लेकिन मेरा सवाल यह है कि यदि कंपाइलर्स/टूलचेन्स इसका समर्थन करते हैं, क्योंकि एम्बेडेड सिस्टम के मामले में, जहां आपके पास पॉलीमोर्फिक ऑब्जेक्ट्स हैं जो छोटे हैं लेकिन कई संख्या में स्थान एक लक्जरी बन जाता है। – ManiP

+0

@ManiP: जैसा कि मैंने कहा, संक्षिप्त जवाब यह है कि मैंने कभी ऐसा टूलचैन कभी नहीं देखा (या सुना)। मैं सहमत हूं कि यह संभव है, और कुछ परिस्थितियों में भी उपयोगी हो सकता है, लेकिन यदि यह किया गया है, तो मैं (एक के लिए) और इससे अनजान हूं। –

2

नहीं, ऐसा कोई स्विच नहीं है। इस काम में अच्छी तरह से में एक बंद कर दिया पदानुक्रम क्योंकि एक भी enum सभी संभव कक्षाओं की गणना कर सकते हैं और उसके बाद प्रत्येक वर्ग एक से जुड़ा हुआ है:

LLVM/बजना codebase कि हजारों की संख्या में से आवंटित किए जाते हैं कक्षाओं में आभासी टेबल से बचा जाता है enum का मूल्य। बंद स्पष्ट रूप से enum की वजह से है।

फिर, enum पर switch द्वारा विधि को लागू किया गया है, और विधि को कॉल करने से पहले उपयुक्त कास्टिंग। एक बार फिर, बंद। प्रत्येक नई कक्षा के लिए switch को संशोधित किया जाना है।


पहला विकल्प: बाहरी vpointer।

यदि आप खुद को ऐसे परिस्थिति में पाते हैं जहां vpointer कर का भुगतान अक्सर किया जाता है, तो अधिकांश वस्तुएं ज्ञात प्रकार के हैं। फिर आप इसे बाहरी कर सकते हैं।

class Interface { 
public: 
    virtual ~Interface() {} 

    virtual Interface* clone() const = 0; // might be worth it 

    virtual void updateCount(int) = 0; 

protected: 
    Interface(Interface const&) {} 
    Interface& operator=(Interface const&) { return *this; } 
}; 

template <typename T> 
class InterfaceBridge: public Interface { 
public: 
    InterfaceBridge(T& t): t(t) {} 

    virtual InterfaceBridge* clone() const { return new InterfaceBridge(*this); } 

    virtual void updateCount(int i) { t.updateCount(i); } 

private: 
    T& t; // value or reference ? Choose... 
}; 

template <typename T> 
InterfaceBridge<T> interface(T& t) { return InterfaceBridge<T>(t); } 

फिर, एक साधारण क्लास की कल्पना:

class Counter { 
public: 
    int getCount() const { return c; } 
    void updateCount(int i) { c = i; } 
private: 
    int c; 
}; 

आप एक सरणी में वस्तुओं स्टोर कर सकते हैं:

void five(Interface& i) { i.updateCount(5); } 

InterfaceBridge<Counter> ib(array[3]); // create *one* v-pointer 
five(ib); 

assert(array[3].getCount() == 5); 
: अभी भी

static Counter array[5]; 

assert(sizeof(array) == sizeof(int)*5); // no v-pointer 

और उनमें बहुरूपी कार्यों के साथ उपयोग करें

मूल्य बनाम संदर्भ वास्तव में एक डिजाइन तनाव है। आम तौर पर, यदि आपको clone की आवश्यकता है तो आपको मूल्य से स्टोर करने की आवश्यकता है, और जब आप बेस क्लास (boost::ptr_vector उदाहरण के लिए) द्वारा स्टोर करते हैं तो आपको क्लोन करना होगा।

Interface <--- ClonableInterface 
    |     | 
InterfaceB  ClonableInterfaceB 

यह सिर्फ अतिरिक्त टाइपिंग है: यह वास्तव में दोनों इंटरफेस (और पुलों) प्रदान करने के लिए संभव है।


एक और समाधान, और अधिक शामिल है।

एक स्विच एक कूद तालिका द्वारा लागू किया जा सकता है। इस तरह की एक तालिका पूरी तरह से उदाहरण के लिए एक std::vector में कार्यावधि में बनाया जा सकता है,:

class Base { 
public: 
    ~Base() { VTables()[vpointer].dispose(*this); } 

    void updateCount(int i) { 
    VTables()[vpointer].updateCount(*this, i); 
    } 

protected: 
    struct VTable { 
    typedef void (*Dispose)(Base&); 
    typedef void (*UpdateCount)(Base&, int); 

    Dispose dispose; 
    UpdateCount updateCount; 
    }; 

    static void NoDispose(Base&) {} 

    static unsigned RegisterTable(VTable t) { 
    std::vector<VTable>& v = VTables(); 
    v.push_back(t); 
    return v.size() - 1; 
    } 

    explicit Base(unsigned id): vpointer(id) { 
    assert(id < VTables.size()); 
    } 

private: 
    // Implement in .cpp or pay the cost of weak symbols. 
    static std::vector<VTable> VTables() { static std::vector<VTable> VT; return VT; } 

    unsigned vpointer; 
}; 

और फिर, एक Derived वर्ग:

class Derived: public Base { 
public: 
    Derived(): Base(GetID()) {} 

private: 
    static void UpdateCount(Base& b, int i) { 
    static_cast<Derived&>(b).count = i; 
    } 

    static unsigned GetID() { 
    static unsigned ID = RegisterTable(VTable({&NoDispose, &UpdateCount})); 
    return ID; 
    } 

    unsigned count; 
}; 

खैर, अब आपको पता चलेगा कि महान यह है कि है कंपाइलर आपके लिए कुछ ओवरहेड की कीमत पर भी करता है।

ओह, और संरेखण के कारण, जैसे ही Derived कक्षा एक सूचक प्रस्तुत करती है, वहां एक जोखिम है कि Base और अगली विशेषता के बीच 4 बाइट पैडिंग का उपयोग किया जाता है। आप पैडिंग से बचने के लिए Derived में पहले कुछ विशेषताओं को सावधानी से चुनकर उनका उपयोग कर सकते हैं ...

+0

यह सुनिश्चित नहीं है कि आप इस कथन से क्या मतलब रखते हैं: "एलएलवीएम/क्लैंग डेटाबेस हजारों लोगों द्वारा आवंटित कक्षाओं में आभासी तालिकाओं से बचाता है"। संकलक कैसे जानता है कि आवेदन में हजारों लोगों द्वारा किस प्रकार की कक्षा आवंटित की जाती है? या क्या मैं इसे गलत व्याख्या कर रहा हूं? – ManiP

+0

@ManiP: मेरा मतलब था * कोडबेस * स्पष्ट रूप से। वैसे भी संकलक नहीं करता है, डेवलपर्स उन पदानुक्रमों में आभासी तरीकों को पेश नहीं करने का प्रयास करते हैं और इसके बजाय मैन्युअल स्विचिंग का सहारा लेते हैं। –

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