2017-12-27 32 views
7

कहें कि आपके पास कक्षा foo है जो किसी प्रकार की कॉल करने योग्य वस्तुओं का संग्रह लपेटती है। foo में एक सदस्य फ़ंक्शन run() है जो संग्रह पर पुन: प्रयास करता है और प्रत्येक फ़ंक्शन ऑब्जेक्ट को कॉल करता है। foo में एक सदस्य remove(...) है जो संग्रह से कॉल करने योग्य ऑब्जेक्ट को हटा देगा।सी ++ में ऐसी स्थिति के खिलाफ सुरक्षा करने के लिए एक बेवकूफ तरीका है जिसमें क्रियाओं का संग्रह चलाने से संग्रह को परिवर्तित किया जा सकता है?

वहाँ एक मुहावरेदार, आरए II शैली गार्ड आप foo.run() में डाल दिया और कर सकता है foo.remove(...) ऐसी है कि हटा देगा कि foo.run() के लिए एक कॉल के द्वारा संचालित किया गया गार्ड नाशक आग जब तक स्थगित कर दिया जाएगा? क्या यह मानक पुस्तकालय में कुछ के साथ किया जा सकता है? क्या इस पैटर्न का नाम है?

मेरा वर्तमान कोड सुरुचिपूर्ण लगता है, इसलिए मैं सबसे अच्छा अभ्यास प्रकार समाधान ढूंढ रहा हूं।

नोट: यह समरूपता के बारे में नहीं है। एक गैर थ्रेड-सुरक्षित समाधान ठीक है। मुद्दा पुन: प्रवेश और आत्म-संदर्भ के साथ है।

यहां समस्या का एक उदाहरण है, सुरुचिपूर्ण "डिफर हटाने" गार्ड को सैन्स करता है।

class ActionPlayer 
{ 
private: 
    std::vector<std::pair<int, std::function<void()>>> actions_; 
public: 
    void addAction(int id, const std::function<void()>& action) 
    { 
     actions_.push_back({ id, action }); 
    } 

    void removeAction(int id) 
    { 
     actions_.erase(
      std::remove_if(
       actions_.begin(), 
       actions_.end(), 
       [id](auto& p) { return p.first == id; } 
      ), 
      actions_.end() 
     ); 
    } 

    void run() 
    { 
     for (auto& item : actions_) { 
      item.second(); 
     } 
    } 
}; 

तो कहीं:

... 

ActionPlayer player; 

player.addAction(1, []() { 
    std::cout << "Hello there" << std::endl; 
}); 

player.addAction(42, [&player]() { 
    std::cout << "foobar" << std::endl; 
    player.removeAction(1); 
}); 

player.run(); // boom 

संपादित करें ... ठीक है कि यह कैसे मैं एक आरए II ताला वस्तु के माध्यम से यह करने के लिए देख सकते हैं। निम्नांकित कार्रवाई को फेंकने और फिर से प्रवेश करने के लिए कॉल को चलाने के लिए चलाना चाहिए, यह मानते हुए कि रिकर्सन अंततः समाप्त हो जाता है (यदि उपयोगकर्ता की गलती नहीं है)। मैंने कैश किए गए std :: फ़ंक्शंस का उपयोग किया क्योंकि इस कोड के वास्तविक संस्करण में addAction और removeAction के समतुल्य टेम्पलेट फ़ंक्शंस हैं जिन्हें केवल वेनिला समरूप टाइप किए गए कंटेनर में संग्रहीत नहीं किया जा सकता है।

class ActionPlayer 
{ 
private: 

    std::vector<std::pair<int, std::function<void()>>> actions_; 
    int run_lock_count_; 
    std::vector<std::function<void()>> deferred_ops_; 

    class RunLock 
    { 
    private: 
     ActionPlayer* parent_; 
    public: 
     RunLock(ActionPlayer* parent) : parent_(parent) { (parent_->run_lock_count_)++; } 
     ~RunLock() 
     { 
      if (--parent_->run_lock_count_ == 0) { 
       while (!parent_->deferred_ops_.empty()) { 
        auto do_deferred_op = parent_->deferred_ops_.back(); 
        parent_->deferred_ops_.pop_back(); 
        do_deferred_op(); 
       } 
      } 
     } 
    }; 

    bool isFiring() const 
    { 
     return run_lock_count_ > 0; 
    } 

public: 
    ActionPlayer() : run_lock_count_(0) 
    { 
    } 

    void addAction(int id, const std::function<void()>& action) 
    { 
     if (!isFiring()) { 
      actions_.push_back({ id, action }); 
     } else { 
      deferred_ops_.push_back(
       [&]() { 
        addAction(id, action); 
       } 
      ); 
     } 
    } 

    void removeAction(int id) 
    { 
     if (!isFiring()) { 
      actions_.erase(
       std::remove_if(
        actions_.begin(), 
        actions_.end(), 
        [id](auto& p) { return p.first == id; } 
       ), 
       actions_.end() 
      ); 
     } else { 
      deferred_ops_.push_back(
       [&]() { 
        removeAction(id); 
       } 
      ); 
     } 
    } 

    void run() 
    { 
     RunLock lock(this); 
     for (auto& item : actions_) { 
      item.second(); 
     } 
    } 
}; 
+0

आप अपने "असजीला 'स्थगित निकालें' गार्ड" क्यों नहीं दिखाते? ऐसा लगता है कि जिस तरह से मैं इसे संपर्क करूंगा। – 1201ProgramAlarm

+0

क्योंकि मेरे पास यह वास्तविक कोड है, यह खिलौना संस्करण नहीं है और यह वास्तविक संरचनाओं से जुड़ा हुआ है जो विविध टेम्पलेट आदि का उपयोग करते हैं। यह एक समस्या में है। मैं एक और सामान्य समाधान खोजने की कोशिश नहीं कर रहा था, जिसे एक्शनप्लेयर-जैसी कक्षाओं के आंतरिक लोगों के बारे में जानने की आवश्यकता नहीं है। वैसे भी, जब मुझे कुछ समय मिलता है तो बीमार उदाहरण अपडेट करें। – jwezorek

उत्तर

1

सामान्य तरीका vector की एक प्रति बनाना है। लेकिन इससे हटाए गए कार्यों को फिर से चलाने का कारण बन सकता है।

void run() 
{ 
    auto actions_copy{actions_}; 
    for (auto& item : actions_copy) { 
     item.second(); 
    } 
} 

अन्य विकल्प इसे हटाया गया तो कार्रवाई चल

अनुमति नहीं है
  1. स्टोर करने के लिए एक bool जोड़े अगर कुछ कार्रवाई निकाल दिया जाता है
  2. उपयोग साझा/कमजोर ptr
  3. उपयोग std::list अगर यह है कि जाना जाता है वर्तमान कार्रवाई को हटाया नहीं जाएगा।
+0

hmmm ... प्रतिलिपि सरल है, लेकिन मैं ऐसी किसी चीज की उम्मीद कर रहा था जो अधिकांश मामलों में स्मृति() में स्मृति आवंटित नहीं करता है जिसमें एक आत्म-संदर्भित निकालना नहीं होता है। मुझे नहीं लगता कि सिर्फ एक std :: सूची में स्विच करने से समस्या – jwezorek

+0

समस्या को हल करती है, फिर आपको किसी क्रिया को हटाए जाने के रूप में चिह्नित करने के लिए 'बूल' जोड़ने के साथ जाना चाहिए और उन्हें 'रन' – balki

1

run पर एक झंडा जोड़ें जो कहता है कि आप actions_ के माध्यम से गणना कर रहे हैं। फिर यदि removeAction को उस ध्वज सेट के साथ बुलाया जाता है, तो आप बाद में हटाने के लिए वेक्टर में id स्टोर करते हैं। गणना करने के दौरान जोड़े गए कार्यों को पकड़ने के लिए आपको एक अलग वेक्टर की भी आवश्यकता हो सकती है। एक बार जब आप actions_ के माध्यम से पुनरावृत्त हो जाते हैं, तो आप उन लोगों को हटा देते हैं जिन्हें हटाया जाना चाहिए और जो जोड़े गए हैं उन्हें जोड़ें।

कुछ

तरह
// within class ActionPlayer, add these private member variables 
private: 
    bool running = false; 
    std::vector<int> idsToDelete; 

public: 
    void run() { 
     running = true; 
     for (auto& item : actions_) { 
      item.second(); 
     } 
     running = false; 
     for (d: idsToDelete) 
      removeAction(d); 
     idsToDelete.clear(); 
    } 

    // ... 

आप आस्थगित addAction कॉल (आप कार्यों के किसी भी एक कार्रवाई में जोड़ सकते हैं अगर करने की आवश्यकता होगी, जिसके लिए एक समान परिवर्तन कर सकते हैं, के बाद से जोड़ने के आवंटन के लिए वेक्टर कारण हो सकता है अधिक भंडारण, वेक्टर को सभी iterators अवैध)।

+0

के अंत में हटा दें कार्यों में से एक फेंकता है हालांकि आपको एक अनिश्चित स्थिति में छोड़ दिया जाएगा। – jwezorek

+0

@jwezorek: तो इसे स्वचालित/साफ करने के लिए प्रयास करें/पकड़ें या 'unique_ptr <टी, फ़ंक्शन >' का उपयोग करें –

0

मैं संरचना को थोड़ा संशोधित करूंगा। ActionPlayer को सीधे संशोधित करने की अनुमति देने के बजाय, मैं बाहरी संशोधक वर्ग के माध्यम से सभी संशोधनों को मजबूर करूंगा। इस उदाहरण में मैंने इसे एक अमूर्त संशोधक वर्ग बनाया है जिसमें विभिन्न ठोस कार्यान्वयन हो सकते हैं (उदा। DeferredModifier, InstantModifier, NullModifier, LoggedModifier, TestModifier .etc।)। आपके कार्यों को अब केवल एक संशोधक के सार आधार वर्ग का संदर्भ लेने की आवश्यकता है और किसी भी add/remove .etc को कॉल करें। आवश्यकतानुसार उस पर कार्य करें .. यह क्रियान्वयन नीति से क्रियान्वयन नीति को अलग करने और कार्यों में विभिन्न संशोधन नीतियों के इंजेक्शन की अनुमति देता है।

यह समवर्ती संशोधन के लिए सरल समर्थन की अनुमति भी देनी चाहिए, क्योंकि अब आपको संशोधनों को रोकने के लिए रन/नहीं चलने वाले राज्य को टॉगल करने की आवश्यकता नहीं है।

यह उदाहरण क्रम में क्रियाओं को फिर से चलाने का एक आसान तरीका दिखाता है (जो एक संपत्ति है जिसे मैं मानता हूं कि आप बनाए रखना चाहते हैं)। एक और उन्नत कार्यान्वयन संशोधन सूची को संशोधित करते समय प्रतिलिपि को कम करने के लिए सभी जोड़/हटाए गए जोड़ों को हटाकर संशोधित सूची सूची को स्कैन कर सकता है और फिर समूह संशोधन/निष्कासन समूह को स्कैन कर सकता है।

कुछ की तरह:

class ActionPlayer { 
friend class Modifier; 
... 

    void run(Modifier &modifier) { } 
private: 
    void addAction(...) { ... } 
    void removeAction(...) { ... } 
} 

class Modifier 
{ 
public: 
    virtual ~Modifier() {} 
    virtual addAction(...) = 0; 
    virtual removeAction(...) = 0; 
} 

class DelayedModifier : public Modifier 
{ 
    struct Modification { virtual void run(ActionPlayer&) = 0; } 

    struct RemoveAction : public Modification 
    { 
     int id; 

     Removal(int _id) : id(_id) {} 
     virtual void run(ActionPlayer &player) { player.removeAction(id); } 
    } 

    struct AddAction : public Modification 
    { 
     int id; 
     std::function<void(Modifier&)>> action; 

     AddAction(int _id, const std::function<void(Modifier&)> &_action) : id(_id), action(_action) {} 
     virtual void run(ActionPlayer &player) { player.addAction(id, action) }; 
    } 

    ActionPlayer &player; 
    std::vector<Modification> modifications; 

public: 
    DelayedModifier(ActionPlayer &_player) player(_player) {} 
    virtual ~DelayedModifier() { run(); } 

    virtual void addAction(int id, const std::function<void(Modifier&)> &action) { modifications.push_back(AddAction(id, action)); } 
    virtual void removeAction(int id) { modifications.push_back(RemoveAction(id)); } 

    void run() 
    { 
     for (auto &modification : modifications) 
      modification.run(player); 
     modifications.clear(); 
    } 
}; 

तो अब लिखें:

ActionPlayer player; 

{ 
    DelayedModifier modifier(player); 

    modifier.addAction(...); 
    modifier.addAction(...); 
    modifier.run(); 
    actions.run(modifier); 
} 
संबंधित मुद्दे

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