c++ - सी++ प्रोग्राम्स




Dynamic_cast के मामले का प्रयोग करें (6)

कई स्थानों पर आप पढ़ सकते हैं कि dynamic_cast अर्थ है "खराब डिज़ाइन"। लेकिन मुझे उपयुक्त उपयोग के साथ कोई लेख नहीं मिल रहा है (अच्छा डिज़ाइन दिखा रहा है, न केवल "उपयोग कैसे करें")।

मैं एक बोर्ड के साथ एक बोर्ड गेम लिख रहा हूं और कई विशेषताओं के साथ वर्णित कई अलग-अलग प्रकार के कार्ड (कुछ कार्ड बोर्ड पर रखे जा सकते हैं)। तो मैंने इसे निम्नलिखित कक्षाओं / इंटरफेस में तोड़ने का फैसला किया:

class Card {};
class BoardCard : public Card {};
class ActionCard : public Card {};
// Other types of cards - but two are enough
class Deck {
    Card* draw_card();
};
class Player {
    void add_card(Card* card);
    Card const* get_card();
};
class Board {
    void put_card(BoardCard const*);
};

कुछ लोगों ने सुझाव दिया कि मुझे कार्ड का वर्णन करने वाले केवल एक वर्ग का उपयोग करना चाहिए। लेकिन मेरा मतलब कई पारस्परिक रूप से गुणों को छोड़कर होगा। और बोर्ड क्लास ' put_card(BoardCard const&) - यह इंटरफ़ेस का एक हिस्सा है कि मैं बोर्ड पर कोई कार्ड नहीं डाल सकता। अगर मेरे पास केवल एक प्रकार का कार्ड था तो मुझे इसे विधि के अंदर देखना होगा।

मैं प्रवाह की तरह निम्नलिखित देखता हूं:

  • एक सामान्य कार्ड डेक में होता है (यह महत्वपूर्ण नहीं है कि इसका प्रकार क्या है)
  • एक सामान्य कार्ड डेक से खींचा जाता है और एक खिलाड़ी को दिया जाता है (ऊपर जैसा ही)
  • यदि कोई खिलाड़ी बोर्डकार्ड चुनता है तो उसे बोर्ड पर रखा जा सकता है

तो मैं बोर्ड पर कार्ड डालने से पहले dynamic_cast उपयोग करता हूं। मुझे लगता है कि कुछ वर्चुअल विधि का उपयोग इस मामले में प्रश्न से बाहर है (इसके अतिरिक्त मैं प्रत्येक कार्ड में बोर्ड के बारे में कुछ कार्रवाई जोड़ने का कोई अर्थ नहीं उठाऊंगा)।

तो मेरा सवाल है: मैंने बुरी तरह से डिजाइन किया है? मैं dynamic_cast से कैसे बच सकता हूं? कुछ प्रकार की विशेषता का उपयोग करना और if एस बेहतर समाधान होगा ...?

पीएस डिजाइन के संदर्भ में dynamic_cast उपयोग के बारे में कोई भी स्रोत इलाज की सराहना से अधिक है।


मैंने बुरी तरह से डिजाइन किया है?

समस्या यह है कि जब भी कोई नया प्रकार का Card पेश किया जाता है तो आपको हमेशा उस कोड को विस्तारित करने की आवश्यकता होती है।

मैं dynamic_cast से कैसे बच सकता हूं?

इससे बचने का सामान्य तरीका इंटरफेस (यानी शुद्ध सार वर्ग) का उपयोग करना है:

struct ICard {
   virtual bool can_put_on_board() = 0;
   virtual ~ICard() {}
};

class BoardCard : public ICard {
public:
    bool can_put_on_board() { return true; };
};

class ActionCard : public ICard {
public:
    bool can_put_on_board() { return false; };
};

इस तरह आप आसानी से ICard लिए एक संदर्भ या सूचक का उपयोग कर सकते हैं और जांच सकते हैं, यदि वास्तविक प्रकार के पास Board पर रखा जा सकता है।

लेकिन मुझे उपयुक्त उपयोग के साथ कोई लेख नहीं मिल रहा है (अच्छा डिज़ाइन दिखा रहा है, न केवल "उपयोग कैसे करें")।

आम तौर पर मैं कहूंगा कि गतिशील कलाकारों के लिए कोई अच्छा, असली जीवन उपयोग के मामले नहीं हैं।

कभी-कभी मैंने इसे सीआरटीपी प्राप्तियों जैसे डीबग कोड में इस्तेमाल किया है

template<typename Derived> 
class Base {
public:
    void foo() {
#ifndef _DEBUG     
      static_cast<Derived&>(*this).doBar();
#else
      // may throw in debug mode if something is wrong with Derived
      // not properly implementing the CRTP
      dynamic_cast<Derived&>(*this).doBar();
#endif
    }
};

dynamic_cast उपयोग क्यों न करें

dynamic_cast आमतौर पर नापसंद होता है क्योंकि इसे आसानी से इस्तेमाल किए गए अबास्ट्रक्शन को तोड़ने के लिए आसानी से दुर्व्यवहार किया जा सकता है। और विशिष्ट कार्यान्वयन पर निर्भर होना बुद्धिमान नहीं है। बेशक इसकी आवश्यकता हो सकती है, लेकिन वास्तव में शायद ही कभी, इसलिए लगभग हर कोई अंगूठे का नियम लेता है - शायद आपको इसका उपयोग नहीं करना चाहिए। यह एक कोड गंध है जो यह इंगित कर सकती है कि आपको अपने अवशेषों पर पुनर्विचार करना चाहिए क्योंकि वे आपके डोमेन में आवश्यक नहीं हो सकते हैं। हो सकता है कि आपके गेम में Board को put_card विधि नहीं होनी चाहिए - शायद इसके बजाय कार्ड में विधि play(const PlaySpace *) होना चाहिए जहां Board PlaySpace या ऐसा कुछ लागू PlaySpace है। यहां तक ​​कि CppCoreGuidelines भी अधिकांश मामलों में dynamic_cast का उपयोग करके हतोत्साहित करते हैं

उपयोग करते समय

आम तौर पर कुछ लोगों को इस तरह की समस्याएं होती हैं लेकिन मैं पहले से ही कई बार आया हूं। समस्या को डबल (या एकाधिक) डिस्पैच कहा जाता है। यहां बहुत पुराना है, लेकिन डबल प्रेषण के बारे में काफी प्रासंगिक लेख (प्रागैतिहासिक auto_ptr ध्यान में रखें): http://www.drdobbs.com/double-dispatch-revisited/184405527

स्कॉट मेयर्स ने अपनी पुस्तकों में से एक में dynamic_cast साथ डबल डिस्पैच मैट्रिक्स बनाने के बारे में कुछ लिखा। लेकिन, सब कुछ, इन गतिशील_कास्ट इस मैट्रिक्स के अंदर 'छिपा' हैं - उपयोगकर्ता नहीं जानते कि किस प्रकार का जादू अंदर होता है।

ध्यान देने योग्य - एकाधिक प्रेषण को कोड गंध भी माना जाता है :-)।

उचित विकल्प

आगंतुक पैटर्न देखें । इसका उपयोग dynamic_cast प्रतिस्थापन के रूप में किया जा सकता है लेकिन यह किसी प्रकार की कोड गंध भी है।

मैं आम तौर पर डिजाइन समस्याओं के लिए अंतिम उपाय उपकरण के रूप में dynamic_cast और विज़िटर का उपयोग करने की सलाह देता हूं क्योंकि वे जटिलता को बढ़ाते हुए अमूर्तता को तोड़ते हैं।


ऐसा लगता है कि दो प्रकार के कार्ड काफी अलग हैं। चीजें बोर्ड कार्ड और एक एक्शन कार्ड कर सकती हैं जो पारस्परिक रूप से अनन्य हैं, और आम बात यह है कि उन्हें डेक से खींचा जा सकता है। इसके अलावा, यह एक चीज नहीं है जो कार्ड करता है, यह एक खिलाड़ी / डेक कार्रवाई है।

यदि यह सत्य है , तो एक प्रश्न पूछना चाहिए कि क्या उन्हें वास्तव में एक सामान्य प्रकार , Card से उतरना चाहिएटैग किए गए यूनियन का एक वैकल्पिक डिज़ाइन होगा: Card इसके बजाय std::variant<BoardCard, ActionCard...> , और उचित प्रकार का एक उदाहरण दें। कार्ड के साथ क्या करना है, यह तय करते समय, आप index() पर switch उपयोग करते हैं और फिर std::get<> केवल उचित प्रकार प्राप्त करते हैं। इस तरह आपको किसी भी *_cast ऑपरेटर की आवश्यकता नहीं है, और प्रत्येक प्रकार के कार्ड का समर्थन करने के तरीकों की पूरी आजादी प्राप्त करें (न ही अन्य प्रकार के लिए समझ में आती है)।

यदि यह केवल सत्य है लेकिन सभी प्रकार के लिए नहीं है, तो आप थोड़ा भिन्न हो सकते हैं: केवल उन प्रकार के कार्डों को समूहबद्ध करें जो समझदारी से सुपरक्लास हो सकते हैं, और उन सामान्य प्रकारों के सेट को variant में डाल सकते हैं।


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

class ICard {
    virtual void put_card(Board* board) = 0;
    virtual void accept(CardVisitor& visitor) = 0; // See later, visitor pattern
}

class ActionCard : public ICard {
    void put_card(Board* board) final {
        // std::cout << "You can't put Action Cards on the board << std::endl;
        // Or just do nothing, if the decision of putting the card on the board
        // is not up to the user
    }
}

class BoardCard : public ICard {
    void put_card(Board* board) final {
        // Whatever implementation puts the card on the board, mb something like:
        board->place_card_on_board(this);
    }
}

class SomeBoardCard : public BoardCard {
    void accept(CardVisitor& visitor) final { // visitor pattern
        visitor.visit(this);
    }
    void print_information(); // see BaseCardVisitor in the next code section
}
class SomeActionCard : public ActionCard {
    void accept(CardVisitor& visitor) final { // visitor pattern
        visitor.visit(this);
    }
    void print_information(); // see BaseCardVisitor
}

class Board {
    void put_card(ICard* const card) {
         card->put_card(this);
    }

    void place_card_on_board(BoardCard* card) {
         // place it on the board
    }
}

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

template <class T>
class BaseCardVisitor {
    void visit(T* card) {
        card->print_information();
    }
}

class CardVisitor : public BaseCardVisitor<SomeBoardCard>,
                    public BaseCardVisitor<SomeActionCard> {

}

class Player {
    void add_card(ICard* card);
    ICard const* get_card();

    void what_is_this_card(ICard* card) {
          card->accept(visitor);
    }

    private:
      CardVisitor visitor;
};

मुझे हमेशा एक कोड गंध का उपयोग मिला, और मेरे अनुभव में, कास्ट 90% समय खराब डिजाइन के कारण था। मैंने कुछ समय-गतिशील अनुप्रयोग में गतिशील_कास्ट का उपयोग देखा जहां यह एकाधिक इंटरफेस से प्राप्त होने के बजाय अधिक प्रदर्शन सुधार प्रदान कर रहा था या वस्तु से किसी प्रकार की गणना (एक प्रकार की तरह) को पुनर्प्राप्त कर रहा था। तो कोड गंध गया, लेकिन उस मामले में गतिशील कास्ट का उपयोग इसके लायक था।

उस ने कहा, मैं आपके मामले में गतिशील कलाकारों के साथ-साथ विभिन्न इंटरफेस से कई विरासतों से बचूंगा।

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

मैं विरासत की बजाय एक रचना के लिए जाना होगा। यह आपको 'फैक्ट्री' के रूप में कार्ड का उपयोग करने का मौका भी प्रदान करेगा:

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

अधिक जानकारी के लिए [ https://en.wikipedia.org/wiki/Composition_over_inheritance देखें]। मैं उद्धरण देना चाहता हूं: संरचना लंबी अवधि में एक अधिक स्थिर व्यापार डोमेन भी प्रदान करती है क्योंकि यह परिवार के सदस्यों के quirks से कम प्रवण है। दूसरे शब्दों में, यह लिखना बेहतर है कि कोई वस्तु क्या कर सकती है (हैस - ए ) यह विस्तारित करने के लिए (आईएस - ए) है। [1]

बोर्डकार्ड / तत्व इस तरह कुछ हो सकता है:

//the card placed on the board.
class BoardElement {
public:
  BoardElement() {}
  virtual ~BoardElement() {};

  //up to you if you want to add a read() methods to read data from the card description (XML / JSON / binary data)
  // but that should not be part of the interface. Talking about a potential "Wizard", it's probably more related to the WizardCard - WizardElement relation/implementation

  //some helpful methods:
  // to be called by the board when placed
  virtual void OnBoard() {}
  virtual void Frame(const float time) { /*do something time based*/ }
  virtual void Draw() {}
  // to be called by the board when removed
  virtual void RemovedFromBoard() {}
};

कार्ड डेक या उपयोगकर्ता के हाथों में इस्तेमाल होने वाली किसी चीज़ का प्रतिनिधित्व कर सकता है, मैं उस तरह का एक इंटरफ़ेस जोड़ूंगा

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

  //that will be invoked by the user in order to provide something to the Board, or NULL if nothing should be added.
  virtual std::shared_ptr<BoardElement*> getBoardElement() { return nullptr; }

  virtual void Frame(const float time) { /*do something time based*/ }
  virtual void Draw() {}

  //usefull to handle resources or internal states
  virtual void OnUserHands() {}
  virtual void Dropped() {}
};

मैं यह जोड़ना चाहता हूं कि यह पैटर्न getBoardElement() विधि के अंदर कई चालों को कारखाने के रूप में कार्य करने से अनुमति देता है (इसलिए कुछ अपने जीवनकाल से उत्पन्न होना चाहिए), एक Card डेटा सदस्य जैसे std:shared_ptr<BoardElement> wizard3D; (उदाहरण के रूप में), Card और BoardElement बीच बाध्यकारी बनाएं:

class WizardBoardElement : public BoardElement {
public:
  WizardBoardElement(const Card* owner);

  // other members omitted ...
};

बाध्यकारी कुछ विन्यास डेटा या जो कुछ भी पढ़ने के लिए उपयोगी हो सकता है ...

तो Card और BoardElement से विरासत का उपयोग आधार वर्गों द्वारा उजागर की गई सुविधाओं को लागू करने के लिए किया जाएगा, न कि अन्य विधियों को प्रदान करने के लिए जो केवल dynamic_cast माध्यम से पहुंचा जा सकता है।

संपूर्णता के लिए:

class Player {
  void add(Card* card) {
    //..
    card->OnUserHands();
    //..
  }

  void useCard(Card* card) {
    //..

    //someway he's got to retrieve the board...
    getBoard()->add(card->getBoardElement());

    //..
  }

  Card const* get_card();
};

class Board {
  void add(BoardElement* el) {
    //..
    el->OnBoard();
    //..
  }
};

इस तरह, हमारे पास कोई गतिशील_कास्ट नहीं है, प्लेयर और बोर्ड सरल कार्ड करते हैं, बिना कार्ड के आंतरिक विवरण के बारे में जानने के बिना, विभिन्न वस्तुओं और रखरखाव में वृद्धि के बीच अच्छे अलगाव प्रदान करते हैं।

ActionCard बारे में बात करते ActionCard , और "प्रभाव" के बारे में बात करना जो अन्य खिलाड़ियों या आपके अवतार पर लागू हो सकता है, हम इस तरह की विधि के बारे में सोच सकते हैं:

enum EffectTarget {
  MySelf,      //a player on itself, an enemy on itself
  MainPlayer,
  Opponents,
  StrongOpponents

  //....
};

class Effect {
public:
  //...
  virtual void Do(Target* target) = 0;
  //...
};

class Card {
public:
  //...
  struct Modifiers {
    EffectTarget eTarget;
    std::shared_ptr<Effect> effect;
  };

  virtual std::vector<Modifiers> getModifiers() { /*...*/ }

  //...
};

class Player : public Target {
public: 

  void useCard(Card* card) {
    //..

    //someway he's got to retrieve the board...
    getBoard()->add(card->getBoardElement());

    auto modifiers = card->getModifiers();
    for each (auto modifier in modifiers)
    {
      //this method is supposed to look at the board, at the player and retrieve the instance of the target 
      Target* target = getTarget(modifier.eTarget);
      modifier.effect->Do(target);
    }

    //..
  }

};

यह कार्ड से प्रभाव लागू करने के लिए एक ही पैटर्न का एक और उदाहरण है, बोर्ड के बारे में ब्योरा जानने के लिए कार्ड से परहेज करना और इसकी स्थिति, जो कार्ड खेल रहा है, और कोड को Player बहुत सरल रखता है।

उम्मीद है कि यह मदद कर सकता है, एक अच्छा दिन, Stefano है।


शायद ही कोई पूरा जवाब है, लेकिन सिर्फ Mark Ransom समान जवाब देने के लिए चाहता था, लेकिन बहुत आम तौर पर बोलते हुए, मुझे उन मामलों में उपयोगी होने के लिए डाउनकास्टिंग मिल गया है जहां बतख टाइपिंग वास्तव में उपयोगी है। कुछ आर्किटेक्चर हो सकते हैं जहां इस तरह की चीजें करने में बहुत उपयोगी होता है:

for each object in scene:
{
     if object can fly:
          make object fly
}

या:

for each object in scene that can fly:
     make object fly

COM इस प्रकार की चीज़ को कुछ हद तक इस तरह की अनुमति देता है:

for each object in scene:
{
     // Request to retrieve a flyable interface from
     // the object.
     IFlyable* flyable = object.query_interface<IFlyable>();

     // If the object provides such an interface, make
     // it fly.
     if (flyable)
          flyable->fly();
}

या:

for each flyable in scene.query<IFlyable>:
     flyable->fly();

यह मध्यस्थ कोड में कुछ रूपों का एक कलाकार है जो पूछताछ और इंटरफेस प्राप्त करने के लिए (पूर्व: IFlyable से IFlyable ) प्राप्त करने के लिए। ऐसे मामलों में, गतिशील कास्ट जांच रन-टाइम प्रकार की जानकारी उपलब्ध सबसे सुरक्षित प्रकार का कास्ट है। सबसे पहले यह देखने के लिए एक सामान्य जांच हो सकती है कि कोई ऑब्जेक्ट इंटरफ़ेस प्रदान करता है जिसमें कास्टिंग शामिल नहीं है। यदि ऐसा नहीं होता है, तो यह query_interface फ़ंक्शन एक शून्य सूचक या कुछ प्रकार के शून्य संभाल / संदर्भ वापस कर सकता है। यदि ऐसा होता है, तो dynamic_cast खिलाफ गतिशील_कास्ट का उपयोग करना वास्तविक सूचक को सामान्य इंटरफ़ेस (पूर्व: IInterface* ) में IFlyable* और क्लाइंट को IFlyable* वापस करने के लिए सबसे सुरक्षित बात है।

एक और उदाहरण इकाई-घटक प्रणाली है। उस मामले में अमूर्त इंटरफेस की पूछताछ के बजाय, हम ठोस घटकों (डेटा) को पुनर्प्राप्त करते हैं:

Flight System:
for each object in scene:
{
     if object.has<Wings>():
          make object fly using object.get<Wings>()
}

या:

for each wings in scene.query<Wings>()
     make wings fly

... इस प्रभाव के लिए कुछ, और यह भी कहीं कास्टिंग का तात्पर्य है।

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

  • ईसीएस को मुझे इतना आसान कारणों में से एक कारण यह है कि इस डोमेन में PhysicsSystem सिस्टम, RenderingSystem सिस्टम, AnimationSystem सिस्टम PhysicsSystem जैसे बहुत PhysicsSystem सिस्टम ट्रांसफॉर्मर और ईसीएस मॉडल को उबालते हैं, उस उद्देश्य के लिए बस उस उद्देश्य के लिए खूबसूरती से फिट बैठते हैं मार्ग। इस डोमेन में COM के साथ, IMotion जैसे मोशन इंटरफ़ेस जैसे इंटरफ़ेस को लागू करने वाले उपप्रकारों की संख्या सैकड़ों में हो सकती है (उदा: PointLight जो 5 अन्य इंटरफेस के साथ IMotion लागू करता है), जिसमें COM इंटरफेस के विभिन्न संयोजनों को लागू करने वाले सैकड़ों वर्गों की आवश्यकता होती है व्यक्तिगत रूप से बनाए रखें। ईसीएस के साथ, यह विरासत पर एक रचना मॉडल का उपयोग करता है, और उन सैकड़ों वर्गों को केवल दो दर्जन सरल घटक structs कम कर देता है जिन्हें उन्हें लिखने वाली संस्थाओं द्वारा अंतहीन तरीकों से जोड़ा जा सकता है, और केवल कुछ मुट्ठी भर प्रणालियों को प्रदान करना होता है व्यवहार: बाकी सब कुछ सिर्फ डेटा है जो इनपुट के रूप में सिस्टम लूप को कुछ आउटपुट प्रदान करता है।

विरासत कोडबेस के बीच जो वैश्विक चर और ब्रूट फोर्स कोडिंग का एक समूह (पूर्व: पॉलिमॉर्फिज्म का उपयोग करने के बजाय सभी जगहों पर सशर्त छिड़काव), गहरी विरासत पदानुक्रम, COM, और ईसीएस, मेरे विशेष डोमेन के लिए रखरखाव के मामले में, मैं चाहता था ECS > COM कहते हैं, जबकि गहरे विरासत पदानुक्रम और ब्रूट फोर्स को पूरे स्थान पर वैश्विक चर के साथ कोडिंग करना दोनों को बनाए रखने के लिए अविश्वसनीय रूप से कठिन था (संरक्षित डेटा फ़ील्ड के साथ गहरी विरासत का उपयोग करने वाले ओओपी को नाविक के रूप में इनवेरिएंट को बनाए रखने के मामले में लगभग मुश्किल है ग्लोबल वेरिएबल्स आईएमओ, लेकिन डिज़ाइन को बदलने की जरूरत होने पर पूरे पदानुक्रमों में फैले सबसे अधिक दुःस्वप्न कैस्केडिंग परिवर्तनों को आमंत्रित कर सकते हैं - कम से कम ब्रूट फोर्स विरासत कोडेबेस में कैस्केडिंग समस्या नहीं थी क्योंकि यह किसी भी कोड को शुरू करने के लिए मुश्किल से उपयोग नहीं कर रहा था) ।

COM और ECS COM के अलावा कुछ हद तक समान हैं, निर्भरता केंद्रीय abstractions की ओर प्रवाह (COM वस्तुओं द्वारा प्रदान की गई COM इंटरफेस, जैसे IFlyable )। ईसीएस के साथ, निर्भरता केंद्रीय डेटा की ओर बहती है (ईसीएस इकाइयों द्वारा प्रदान किए गए घटक, Wings तरह)। दोनों के दिल में अक्सर यह विचार होता है कि हमारे पास ब्याज की गैर-सजातीय वस्तुओं (या "संस्थाएं") का समूह है, जिनके मुहैया कराए गए इंटरफेस या घटकों को पहले से नहीं जाना जाता है, क्योंकि हम उन्हें एक गैर-सजातीय संग्रह के माध्यम से एक्सेस कर रहे हैं (उदा: एक "दृश्य")। नतीजतन, हमें इस गैर-सजातीय संग्रह के माध्यम से संग्रह या वस्तुओं को व्यक्तिगत रूप से पूछताछ करके यह देखने के लिए रनटाइम पर अपनी क्षमताओं की खोज करने की आवश्यकता होती है।

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

उस ने कहा, रनटाइम जांच की एक छोटी सी लागत है। आमतौर पर यदि dynamic_cast का उपयोग COM और ECS आर्किटेक्चर में किया जाता है, तो यह एक तरीके से किया जाता है ताकि एक std::bad_cast कभी नहीं फेंक दिया जाए और / या nullptr स्वयं nullptr वापस नहीं लौटाता है ( nullptr सिर्फ यह सुनिश्चित करने के लिए एक nullptr चेक है कि कोई नहीं है आंतरिक प्रोग्रामर त्रुटियां, यह निर्धारित करने के तरीके के रूप में नहीं कि कोई ऑब्जेक्ट किसी प्रकार को प्राप्त करता है)। एक और प्रकार का रनटाइम चेक उस से बचने के लिए किया जाता है (उदा: सभी PosAndVelocity घटकों को लाने के दौरान एक ईसीएस में पूरी क्वेरी के लिए केवल एक बार यह निर्धारित करने के लिए कि कौन सी घटक सूची उपयोग करने के लिए वास्तव में सजातीय है और केवल PosAndVelocity घटकों को स्टोर PosAndVelocity है)। यदि वह छोटी रनटाइम लागत गैर-नगण्य है क्योंकि आप प्रत्येक फ्रेम के घटकों के बोतलबंद पर लूपिंग कर रहे हैं और प्रत्येक को तुच्छ काम कर रहे हैं, तो मुझे यह स्निपेट सी ++ कोडिंग मानकों में हर्ब सटर से उपयोगी पाया गया है:

template<class To, class From> To checked_cast(From* from) {
    assert( dynamic_cast<To>(from) == static_cast<To>(from) && "checked_cast failed" );
    return static_cast<To>(from);
}

template<class To, class From> To checked_cast(From& from) {
    assert( dynamic_cast<To>(from) == static_cast<To>(from) && "checked_cast failed" );
    return static_cast<To>(from);
}

यह मूल रूप से डीबग बिल्ड के लिए एक सिक्योरिटी चेक के रूप में dynamic_cast का उपयोग करता है, और रिलीज बिल्ड के लिए static_cast





reference