12

मुझे कुछ विरासत कोड मिला है, वर्चुअल फ़ंक्शंस के बजाय, गतिशील प्रेषण करने के लिए kind फ़ील्ड का उपयोग करता है।सी ++ वर्चुअल फ़ंक्शंस के बिना डायनामिक डिस्पैच

// Base struct shared by all subtypes 
// Plain-old data; can't use virtual functions 
struct POD 
{ 
    int kind; 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
    int GetXyzzy(); 
}; 

enum Kind { Kind_Derived1, Kind_Derived2, Kind_Derived3 /* , ... */ }; 

struct Derived1: POD 
{ 
    Derived1(): kind(Kind_Derived1) {} 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
    int GetXyzzy(); 

    // ... plus other type-specific data and function members ... 
}; 

struct Derived2: POD 
{ 
    Derived2(): kind(Kind_Derived2) {} 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
    int GetXyzzy(); 

    // ... plus other type-specific data and function members ... 
}; 

struct Derived3: POD 
{ 
    Derived3(): kind(Kind_Derived3) {} 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
    int GetXyzzy(); 

    // ... plus other type-specific data and function members ... 
}; 

// ... and so on for other derived classes ... 

और फिर POD वर्ग के समारोह के सदस्यों को इस तरह लागू किया जाता है:: यह कुछ इस तरह दिखता

int POD::GetFoo() 
{ 
    // Call kind-specific function 
    switch (kind) 
    { 
    case Kind_Derived1: 
     { 
     Derived1 *pDerived1 = static_cast<Derived1*>(this); 
     return pDerived1->GetFoo(); 
     } 
    case Kind_Derived2: 
     { 
     Derived2 *pDerived2 = static_cast<Derived2*>(this); 
     return pDerived2->GetFoo(); 
     } 
    case Kind_Derived3: 
     { 
     Derived3 *pDerived3 = static_cast<Derived3*>(this); 
     return pDerived3->GetFoo(); 
     } 

    // ... and so on for other derived classes ... 

    default: 
     throw UnknownKindException(kind, "GetFoo"); 
    } 
} 

POD::GetBar(), POD::GetBaz(), POD::GetXyzzy(), और अन्य सदस्यों इसी तरह लागू किया जाता है।

यह उदाहरण सरलीकृत है। वास्तविक कोड में POD, और कुछ दर्जन विधियों के लगभग दर्जन विभिन्न उपप्रकार हैं। POD के नए उपप्रकार और नए तरीके बहुत बार जोड़े जाते हैं, और इसलिए जब भी हम ऐसा करते हैं, तो हमें इन सभी switch कथन अपडेट करना होगा।

POD कक्षा में फ़ंक्शन सदस्यों virtual को घोषित करने का सामान्य तरीका होगा, लेकिन हम ऐसा नहीं कर सकते क्योंकि ऑब्जेक्ट साझा स्मृति में रहते हैं। बहुत सारे कोड हैं जो इन structs पर सादे-पुराने डेटा होने पर निर्भर करते हैं, इसलिए यदि मैं साझा-स्मृति वस्तुओं में वर्चुअल फ़ंक्शंस रखने का कोई तरीका समझ सकता हूं, तो मैं ऐसा नहीं करना चाहूंगा।

तो, मैं इसे साफ करने के सर्वोत्तम तरीके के रूप में सुझावों की तलाश कर रहा हूं ताकि उप-प्रकार के तरीकों को कॉल करने के बारे में सभी ज्ञान एक स्थान पर केंद्रीकृत हो, बजाय दो दर्जन switch विवरणों में बिखरे हुए जोड़े दर्जन कार्यों।

मेरे लिए क्या होता है कि मैं कुछ प्रकार के एडाप्टर वर्ग बना सकता हूं जो POD को लपेटता है और अनावश्यकता को कम करने के लिए टेम्पलेट का उपयोग करता है। लेकिन इससे पहले कि मैं उस रास्ते को शुरू कर दूं, मैं जानना चाहूंगा कि दूसरों ने इसका सामना कैसे किया है।

+0

तुमने कहा था एक बहुत वहाँ था इस वर्ग के आधार पर कोड का। क्या आप इसमें फ़ील्ड जोड़ सकते हैं, या क्या संरचना को वही रहने की आवश्यकता है? –

+0

संरचना मूल रूप से वही रहनी चाहिए। हमारे पास साझा स्मृति में इन चीजों के विशाल सरणी का एक गुच्छा है, और पहले से ही स्मृति-आकार सीमाओं में बंपिंग कर रहे हैं। –

+0

क्या सभी एकाधिक प्रक्रियाओं में लाइब्रेरी का सटीक संस्करण है, या नहीं? –

उत्तर

11

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

template<typename T> int get_derived_foo(POD*ptr) { 
    return static_cast<T>(ptr)->GetFoo(); 
} 
int (*)(POD*) funcs[] = { 
    get_derived_foo<Derived1>, 
    get_derived_foo<Derived2>, 
    get_derived_foo<Derived3> 
}; 
int POD::GetFoo() { 
    return funcs[kind](this); 
} 

एक संक्षिप्त उदाहरण के लिए।

साझा स्मृति में होने की सीमाएं क्या हैं? मुझे एहसास हुआ कि मुझे यहां पर्याप्त जानकारी नहीं है। क्या इसका मतलब यह है कि मैं पॉइंटर्स का उपयोग नहीं कर सकता, क्योंकि किसी अन्य प्रक्रिया में कोई भी उन पॉइंटर्स का उपयोग करने की कोशिश करेगा?

आप एक स्ट्रिंग मानचित्र का उपयोग कर सकते हैं, जहां प्रत्येक प्रक्रिया को मानचित्र की अपनी प्रति प्राप्त होती है। आपको इसे GetFoo() में पास करना होगा ताकि वह इसे पा सके।

struct POD { 
    int GetFoo(std::map<int, std::function<int()>& ref) { 
     return ref[kind](); 
    } 
}; 

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

+0

साझा स्मृति में ऑब्जेक्ट्स कई प्रक्रियाओं द्वारा उपयोग किया जाता है। वर्चुअल फ़ंक्शन पॉइंटर सभी प्रक्रियाओं के लिए मान्य नहीं होगा। –

+2

मैंने विवरणों में ध्यान नहीं दिया है, लेकिन सामान्य रूप से, तालिका को प्रत्येक ऑब्जेक्ट में पॉइंटर द्वारा संदर्भित किया जाता है। यदि किसी धागे में पता वाई पर टाइप एक्स के लिए vtable है, तो यह ऑब्जेक्ट के vptr फ़ील्ड में वाई स्टोर करेगा।यह गारंटी नहीं है कि भले ही vtable सिस्टम में सटीक उसी हार्डवेयर पते में संग्रहीत किया गया हो, फिर भी दो अलग-अलग प्रक्रियाएं इसे विभिन्न लॉजिकल पतों में नहीं देख पाएंगी। यदि ऐसा है, तो अन्य धागा गलत पते में vtable का उपयोग करने और मरने का प्रयास करेगा। –

+1

अच्छा, लेकिन यह सुनिश्चित करना बहुत अच्छा होगा कि आप सरणी को सही ढंग से उत्पन्न करते हैं। एक प्रीप्रोसेसर मैक्रो इसे कर सकता है। –

1

आप Curiously recurring template pattern के साथ प्रयोग कर सकते हैं। यह थोड़ा जटिल है, लेकिन जब आप शुद्ध आभासी कार्यों का उपयोग नहीं कर सकते हैं तो यह सहायक हो सकता है।

0

यहां उत्सुकता से आवर्ती टेम्पलेट पैटर्न का उपयोग करके एक उदाहरण दिया गया है। यदि आप संकलन समय पर अधिक जानकारी जानते हैं तो यह आपकी आवश्यकताओं के अनुरूप हो सकता है।

template<class DerivedType> 
struct POD 
{ 
    int GetFoo() 
    { 
     return static_cast<DerivedType*>(this)->GetFoo(); 
    } 
    int GetBar() 
    { 
     return static_cast<DerivedType*>(this).GetBar(); 
    } 
    int GetBaz() 
    { 
     return static_cast<DerivedType*>(this).GetBaz(); 
    } 
    int GetXyzzy() 
    { 
     return static_cast<DerivedType*>(this).GetXyzzy(); 
    } 
}; 

struct Derived1 : public POD<Derived1> 
{ 
    int GetFoo() 
    { 
     return 1; 
    } 
    //define all implementations 
}; 

struct Derived2 : public POD<Derived2> 
{ 
    //define all implementations 

}; 

int main() 
{ 
    Derived1 d1; 
    cout << d1.GetFoo() << endl; 
    POD<Derived1> *p = new Derived1; 
    cout << p->GetFoo() << endl; 
    return 0; 
} 
+0

लेकिन अगर मैं 'पीओडी *' से शुरू होता हूं, तो वास्तव में एक उप प्रकार को इंगित करता हूं, और 'GetFoo()' को कॉल करना चाहता हूं तो मैं क्या करूँ? –

1

यहाँ एक दृष्टिकोण आभासी तरीकों का उपयोग करता है, कूद तालिका लागू करने के लिए Pod वर्ग या व्युत्पन्न वर्ग की आवश्यकता होती है वास्तव में आभासी कार्यों के लिए बिना है।

उद्देश्य कई वर्गों में विधियों को जोड़ने और हटाने को सरल बनाना है।

एक विधि जोड़ने के लिए, इसे एक स्पष्ट और सामान्य पैटर्न का उपयोग करके पॉड में जोड़ा जाना आवश्यक है, शुद्ध वर्चुअल फ़ंक्शन को PodInterface में जोड़ा जाना आवश्यक है, और एक स्पष्ट और सामान्य पैटर्न का उपयोग करके फ़ॉरवर्डिंग फ़ंक्शन को PodFuncs में जोड़ा जाना चाहिए।

व्युत्पन्न कक्षाओं में चीजों को सेट करने के लिए केवल एक फ़ाइल स्थैतिक प्रारंभिक वस्तु की आवश्यकता होती है, अन्यथा वे पहले से ही बहुत कुछ दिखते हैं।

// Pod header 

#include <boost/shared_ptr.hpp> 
enum Kind { Kind_Derived1, Kind_Derived2, Kind_Derived3 /* , ... */ }; 

struct Pod 
{ 
    int kind; 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
}; 

struct PodInterface 
{ 
    virtual ~PodInterface(); 

    virtual int GetFoo(Pod* p) const = 0; 
    virtual int GetBar(Pod* p) const = 0; 
    virtual int GetBaz(Pod* p) const = 0; 

    static void 
    do_init(
      boost::shared_ptr<PodInterface const> const& p, 
      int kind); 
}; 

template<class T> struct PodFuncs : public PodInterface 
{ 
    struct Init 
    { 
     Init(int kind) 
     { 
      boost::shared_ptr<PodInterface> t(new PodFuncs); 
      PodInterface::do_init(t, kind); 
     } 
    }; 

    ~PodFuncs() { } 

    int GetFoo(Pod* p) const { return static_cast<T*>(p)->GetFoo(); } 
    int GetBar(Pod* p) const { return static_cast<T*>(p)->GetBar(); } 
    int GetBaz(Pod* p) const { return static_cast<T*>(p)->GetBaz(); } 
}; 


// 
// Pod Implementation 
// 

#include <map> 

typedef std::map<int, boost::shared_ptr<PodInterface const> > FuncMap; 

static FuncMap& get_funcmap() 
{ 
    // Replace with other approach for static initialisation order as appropriate. 
    static FuncMap s_funcmap; 
    return s_funcmap; 
} 

// 
// struct Pod methods 
// 

int Pod::GetFoo() 
{ 
    return get_funcmap()[kind]->GetFoo(this); 
} 

// 
// struct PodInterface methods, in same file as s_funcs 
// 

PodInterface::~PodInterface() 
{ 
} 

void 
PodInterface::do_init(
     boost::shared_ptr<PodInterface const> const& p, 
     int kind) 
{ 
    // Could do checking for duplicates here. 
    get_funcmap()[kind] = p; 
} 

// 
// Derived1 
// 

struct Derived1 : Pod 
{ 
    Derived1() { kind = Kind_Derived1; } 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 

    // Whatever else. 
}; 

// 
// Derived1 implementation 
// 

static const PodFuncs<Derived1>::Init s_interface_init(Kind_Derived1); 

int Derived1::GetFoo() { /* Implement */ } 
int Derived1::GetBar() { /* Implement */ } 
int Derived1::GetBaz() { /* Implement */ } 
1

यहां टेम्पलेट-मेटाप्रोग्रामिंग पथ है जो मैं नीचे जा रहा हूं।

  • एक नई तरह के लिए समर्थन जोड़ा जा रहा है केवल LAST_KIND को अद्यतन करने के लिए और एक नया KindTraits जोड़ने की आवश्यकता है: यहाँ मैं इसके बारे में क्या पसंद है।
  • नया फ़ंक्शन जोड़ने के लिए एक सरल पैटर्न है।
  • यदि आवश्यक हो तो कार्यों को विशेष प्रकार के लिए विशेषीकृत किया जा सकता है।
  • यदि मैं कुछ भी पेंच करता हूं, तो मैं रहस्यमय रन-टाइम दुर्व्यवहार की बजाय संकलन-समय त्रुटियों और चेतावनियों की अपेक्षा कर सकता हूं।

चिंताओं के एक जोड़े हैं:

  • POD के कार्यान्वयन अब सभी व्युत्पन्न वर्ग के इंटरफेस पर निर्भर है। (यह मौजूदा कार्यान्वयन में पहले से ही सच है, इसलिए मैं इसके बारे में चिंतित नहीं हूं, लेकिन यह एक गंध है।)
  • मैं संकलक पर गिनती कर रहा हूं जो कि कोड उत्पन्न करने के लिए काफी स्मार्ट हो switch-आधारित कोड पर।
  • कई सी ++ प्रोग्रामर इसे देखकर अपने सिर खरोंच करेंगे।

कोड यह रहा:

// Declare first and last kinds 
const int FIRST_KIND = Kind_Derived1; 
const int LAST_KIND = Kind_Derived3; 

// Provide a compile-time mapping from a kind code to a subtype 
template <int KIND> 
struct KindTraits 
{ 
    typedef void Subtype; 
}; 
template <> KindTraits<Kind_Derived1> { typedef Derived1 Subtype; }; 
template <> KindTraits<Kind_Derived2> { typedef Derived2 Subtype; }; 
template <> KindTraits<Kind_Derived3> { typedef Derived3 Subtype; }; 

// If kind matches, then do the appropriate typecast and return result; 
// otherwise, try the next kind. 
template <int KIND> 
int GetFooForKind(POD *pod) 
{ 
    if (pod->kind == KIND) 
     return static_cast<KindTraits<KIND>::Subtype>(pod)->GetFoo(); 
    else 
     return GetFooForKind<KIND + 1>(); // try the next kind 
} 

// Specialization for LAST_KIND+1 
template <> int GetFooForKind<LAST_KIND + 1>(POD *pod) 
{ 
    // kind didn't match anything in FIRST_KIND..LAST_KIND 
    throw UnknownKindException(kind, "GetFoo"); 
} 

// Now POD's function members can be implemented like this: 

int POD::GetFoo() 
{ 
    return GetFooForKind<FIRST_KIND>(this); 
} 
+1

आप एक UnkindException फेंकने का मौका चूक गए! इस दृष्टिकोण के लिए प्रत्येक व्युत्पन्न प्रकार की परिभाषा को देखने के लिए पीओडी कार्यान्वयन की आवश्यकता है। कोड की वर्तमान स्थिति को देखते हुए, आपको परवाह नहीं है, लेकिन आवश्यकता है। – janm

+0

मैंने अपने "चिंताएं" खंड पर निर्भरता के बारे में नोट जोड़ा है। –

0

समाधान आप के साथ समाप्त हो गया पर विस्तार, निम्नलिखित कार्यक्रम प्रारंभ में व्युत्पन्न कार्यों के लिए मानचित्रण हल करती है:

#include <typeinfo> 
#include <iostream> 
#include <functional> 
#include <vector> 

enum Kind 
{ 
    Kind_First, 
    Kind_Derived1 = Kind_First, 
    Kind_Derived2, 
    Kind_Total 
}; 

struct POD 
{ 
    size_t kind; 

    int GetFoo(); 
    int GetBar(); 
}; 

struct VTable 
{ 
    std::function<int(POD*)> GetFoo; 
    std::function<int(POD*)> GetBar; 
}; 

template<int KIND> 
struct KindTraits 
{ 
    typedef POD KindType; 
}; 

template<int KIND> 
void InitRegistry(std::vector<VTable> &t) 
{ 
    typedef typename KindTraits<KIND>::KindType KindType; 

    size_t i = KIND; 
    t[i].GetFoo = [](POD *p) -> int { 
     return static_cast<KindType*>(p)->GetFoo(); 
    }; 
    t[i].GetBar = [](POD *p) -> int { 
     return static_cast<KindType*>(p)->GetBar(); 
    }; 

    InitRegistry<KIND+1>(t); 
} 
template<> 
void InitRegistry<Kind_Total>(std::vector<VTable> &t) 
{ 
} 

struct Registry 
{ 
    std::vector<VTable> table; 

    Registry() 
    { 
     table.resize(Kind_Total); 
     InitRegistry<Kind_First>(table); 
    } 
}; 

Registry reg; 

int POD::GetFoo() { return reg.table[kind].GetFoo(this); } 
int POD::GetBar() { return reg.table[kind].GetBar(this); } 

struct Derived1 : POD 
{ 
    Derived1() { kind = Kind_Derived1; } 

    int GetFoo() { return 0; } 
    int GetBar() { return 1; } 
}; 
template<> struct KindTraits<Kind_Derived1> { typedef Derived1 KindType; }; 

struct Derived2 : POD 
{ 
    Derived2() { kind = Kind_Derived2; } 

    int GetFoo() { return 2; } 
    int GetBar() { return 3; } 
}; 
template<> struct KindTraits<Kind_Derived2> { typedef Derived2 KindType; }; 

int main() 
{ 
    Derived1 d1; 
    Derived2 d2; 
    POD *p; 

    p = static_cast<POD*>(&d1); 
    std::cout << p->GetFoo() << '\n'; 
    p = static_cast<POD*>(&d2); 
    std::cout << p->GetBar() << '\n'; 
}