कॉन्स्ट सी ++ DRY रणनीतियाँ


14

गैर-तुच्छ सी ++ कब्ज से संबंधित दोहराव से बचने के लिए, क्या ऐसे मामले हैं जहां कॉन्स्टोकैस्ट काम करेगा, लेकिन एक गैर-कॉन्स्टेबल फ़ंक्शन गैर-कॉन्स्टेंट को वापस करेगा?

स्कॉट मेयर्स के प्रभावी C ++ आइटम 3 में, वह सुझाव देते हैं कि एक स्थिर कलाकार के साथ संयुक्त एक const_cast डुप्लिकेट कोड से बचने के लिए एक प्रभावी और सुरक्षित तरीका हो सकता है, उदा।

const void* Bar::bar(int i) const
{
  ...
  return variableResultingFromNonTrivialDotDotDotCode;
}
void* Bar::bar(int i)
{
  return const_cast<void*>(static_cast<const Bar*>(this)->bar(i));
}

मेयर बताते हैं कि कॉन्स्ट फंक्शन को नॉन-कॉस्ट फंक्शन कहना खतरनाक है।

नीचे दिया गया कोड एक काउंटर-उदाहरण दिखा रहा है:

  • मेयर्स के सुझाव के विपरीत, कभी-कभी स्थिर कलाकारों के साथ संयुक्त const_cast खतरनाक होता है
  • कभी-कभी कांस्ट फ़ंक्शन को नॉन-कॉस्ट कहना कम खतरनाक होता है
  • कभी-कभी एक const_cast का उपयोग करने के दोनों तरीके संभावित उपयोगी संकलक त्रुटियों को छिपाते हैं
  • एक const_cast से परहेज करना और एक अतिरिक्त कॉन्स्टेबल प्राइवेट मेंबर को नॉन-कॉस्ट वापस करना एक और विकल्प है

क्या कोड डुप्लीकेशन से बचने की कास्ट_कास्ट रणनीतियों में से कोई भी अच्छा अभ्यास माना जाता है? क्या आप इसके बजाय निजी पद्धति की रणनीति पसंद करेंगे? क्या ऐसे मामले हैं जहां const_cast काम करेगा लेकिन एक निजी तरीका नहीं होगा? क्या अन्य विकल्प (नकल के अलावा) हैं?

Const_cast रणनीतियों के साथ मेरी चिंता यह है कि भले ही कोड सही होने पर लिखा जाए, बाद में रखरखाव के दौरान कोड गलत हो सकता है और const_cast एक उपयोगी संकलक त्रुटि छिपाएगा। ऐसा लगता है कि एक आम निजी समारोह आम तौर पर सुरक्षित है।

class Foo
{
  public:
    Foo(const LongLived& constLongLived, LongLived& mutableLongLived)
    : mConstLongLived(constLongLived), mMutableLongLived(mutableLongLived)
    {}

    // case A: we shouldn't ever be allowed to return a non-const reference to something we only have a const reference to

    // const_cast prevents a useful compiler error
    const LongLived& GetA1() const { return mConstLongLived; }
    LongLived& GetA1()
    {
      return const_cast<LongLived&>( static_cast<const Foo*>(this)->GetA1() );
    }

    /* gives useful compiler error
    LongLived& GetA2()
    {
      return mConstLongLived; // error: invalid initialization of reference of type 'LongLived&' from expression of type 'const LongLived'
    }
    const LongLived& GetA2() const { return const_cast<Foo*>(this)->GetA2(); }
    */

    // case B: imagine we are using the convention that const means thread-safe, and we would prefer to re-calculate than lock the cache, then GetB0 might be correct:

    int GetB0(int i) { return mCache.Nth(i); }
    int GetB0(int i) const { return Fibonachi().Nth(i); }

    /* gives useful compiler error
    int GetB1(int i) const { return mCache.Nth(i); } // error: passing 'const Fibonachi' as 'this' argument of 'int Fibonachi::Nth(int)' discards qualifiers
    int GetB1(int i)
    {
      return static_cast<const Foo*>(this)->GetB1(i);
    }*/

    // const_cast prevents a useful compiler error
    int GetB2(int i) { return mCache.Nth(i); }
    int GetB2(int i) const { return const_cast<Foo*>(this)->GetB2(i); }

    // case C: calling a private const member that returns non-const seems like generally the way to go

    LongLived& GetC1() { return GetC1Private(); }
    const LongLived& GetC1() const { return GetC1Private(); }

  private:
    LongLived& GetC1Private() const { /* pretend a bunch of lines of code instead of just returning a single variable*/ return mMutableLongLived; }

    const LongLived& mConstLongLived;
    LongLived& mMutableLongLived;
    Fibonachi mCache;
};

class Fibonachi
{ 
    public:
      Fibonachi()
      {
        mCache.push_back(0);
        mCache.push_back(1);
      }

      int Nth(int n) 
      {
        for (int i=mCache.size(); i <= n; ++i)
        {
            mCache.push_back(mCache[i-1] + mCache[i-2]);
        }
        return mCache[n];
      }

      int Nth(int n) const
      {
          return n < mCache.size() ? mCache[n] : -1;
      }
    private:
      std::vector<int> mCache;
};

class LongLived {};

एक गेट्टर जो सिर्फ एक सदस्य को लौटाता है वह एक से कम है जो खुद को अन्य संस्करण कहता है। चाल अधिक जटिल कार्यों के लिए अभिप्रेत है जहां कटौती का लाभ कास्टिंग के जोखिमों को दूर करता है।
सेबेस्टियन रेडल

@ सेबैस्टियनरेडल मैं मानता हूं कि सिर्फ रिटर्निंग मेंबर होने पर नकल बेहतर होगी। कृपया कल्पना करें कि यह अधिक जटिल है, उदाहरण के लिए mConstLongLived को वापस करने के बजाय, हम mConstLongLived पर एक फ़ंक्शन को कॉल कर सकते हैं जो एक कॉन्स्ट रेफरेंस देता है जो तब एक अन्य फ़ंक्शन को कॉल करने के लिए उपयोग किया जाता है जो एक कॉन्स्टेंट रेफ़रेंस लौटाता है जो हमारे पास है और केवल इसके लिए एक्सेस है का एक कास्ट संस्करण। मुझे उम्मीद है कि यह बात स्पष्ट है कि const_cast कुछ से कब्ज को दूर कर सकता है जिसे हम अन्यथा नॉन-कास्ट एक्सेस नहीं करेंगे।
JDiMatteo

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

जवाबों:


8

जब कास्ट और नॉन-कास्ट सदस्य कार्यों को लागू करते हैं जो केवल इस बात से भिन्न होते हैं कि क्या रिटर्न पीटीआर / संदर्भ कास्ट है, तो सबसे अच्छी DRY रणनीति निम्नलिखित है:

  1. यदि कोई एक्सेसर लिख रहा है, तो विचार करें कि क्या आपको वास्तव में एक्सेसर की आवश्यकता है, cmaster का उत्तर देखें और http://c2.com/cgi/wiki?AccessorsAreEvil
  2. कोड को डुप्लिकेट करें यदि यह तुच्छ है (उदाहरण के लिए सिर्फ एक सदस्य लौटाता है)
  3. const से संबंधित दोहराव से बचने के लिए कभी भी const_cast का उपयोग न करें
  4. गैर-तुच्छ दोहराव से बचने के लिए, एक निजी कॉन्स्टेबल फ़ंक्शन का उपयोग करें जो एक गैर-कॉन्स्टेंट को लौटाता है, जो कॉन्स और नॉन-कॉस्ट सार्वजनिक कार्यों को कॉल करता है

जैसे

public:
  LongLived& GetC1() { return GetC1Private(); }
  const LongLived& GetC1() const { return GetC1Private(); }
private:
  LongLived& GetC1Private() const { /* non-trivial DRY logic here */ }

चलिए इसे गैर-कास्ट पैटर्न लौटाने वाला निजी कांस्ट फ़ंक्शन कहते हैं ।

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


आपकी दलीलें पूरी तरह से आश्वस्त करने वाली हैं, लेकिन मैं इस बात से हैरान हूं कि आप एक constउदाहरण से किसी चीज़ के लिए एक गैर-कॉन्स्टेबल संदर्भ कैसे प्राप्त कर सकते हैं (जब तक कि संदर्भ कुछ घोषित नहीं किया जाता है mutable, या जब तक आप एक नौकरी नहीं करते हैं, const_castलेकिन दोनों मामलों में शुरू होने की कोई संभावना नहीं है। )। इसके अलावा, मैं "निजी कांस्ट फ़ंक्शन को नॉन-कास्ट पैटर्न लौटाने वाले" पर कुछ भी पा सकता था (यदि इसे पैटर्न कहने के लिए मज़ाक का इरादा था .... यह हास्यास्पद नहीं है;)
idclev 463035818

1
यहाँ प्रश्न में कोड के आधार पर एक संकलन उदाहरण है: ideone.com/wBE1GB । क्षमा करें, मेरा मतलब यह मजाक के रूप में नहीं था, लेकिन मैंने इसका मतलब यहां एक नाम देने के लिए किया था (अप्रत्याशित घटना में यह एक नाम के योग्य है), और मैंने उत्तर में शब्दों को अद्यतन करने का प्रयास किया ताकि वह और अधिक स्पष्ट हो सके। यह लिखे हुए मुझे कुछ साल हो गए हैं, और मुझे याद नहीं है कि मैंने क्यों सोचा था कि कंस्ट्रक्टर में एक संदर्भ को पारित करना प्रासंगिक था।
JDiMatteo

उदाहरण के लिए धन्यवाद, मेरे पास अभी समय नहीं है, लेकिन मैं निश्चित रूप से इसमें वापस आऊंगा। Fwiw यहां एक उत्तर है जो समान दृष्टिकोण प्रस्तुत करता है और टिप्पणियों में समान मुद्दों को इंगित किया गया है: stackoverflow.com/a/124209/4117728
idclev 463035818

1

हां, आप सही हैं: कई सी ++ प्रोग्राम जो कॉन्स्ट-करेक्शन का प्रयास करते हैं, DRY सिद्धांत के उल्लंघन में हैं, और यहां तक ​​कि गैर-कॉन्स्टेंट को लौटाने वाला निजी सदस्य आराम के लिए बहुत अधिक जटिलता है।

हालाँकि, आप एक अवलोकन को याद करते हैं: यदि आप अपने डेटा सदस्यों को अन्य कोड एक्सेस दे रहे हैं, तो कॉस्ट-करेक्शन के कारण कोड डुप्लिकेट केवल एक समस्या है। यह अपने आप में अतिक्रमण का उल्लंघन है। आम तौर पर, इस तरह का कोड दोहराव ज्यादातर साधारण एक्सेसर्स में होता है (आखिरकार, आप पहले से मौजूद सदस्यों तक पहुंच सौंप रहे हैं, रिटर्न वैल्यू आम तौर पर गणना का परिणाम नहीं है)।

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


यह बहस के लिए लगता है कि एक्सेसर्स अच्छे हैं या नहीं, उदाहरण के लिए c2.com/cgi/wiki?AccessorsAreEvil पर चर्चा देखें । व्यवहार में, चाहे आप एक्सेसर्स के बारे में सोचते हों, बड़े कोड बेस अक्सर उनका उपयोग करते हैं, और यदि वे उनका उपयोग करते हैं तो DRY सिद्धांत का पालन करना बेहतर होगा। इसलिए मुझे लगता है कि सवाल इस बात का जवाब देने के अधिक योग्य है कि आपको यह नहीं पूछना चाहिए।
JDiMatteo

1
यह निश्चित रूप से पूछने लायक एक प्रश्न है :-) और मैं इस बात से भी इनकार नहीं करूंगा कि आपको समय-समय पर एक्सेसर्स की आवश्यकता है। मैं केवल यह कह रहा हूं कि एक प्रोग्रामिंग शैली जो एक्सेसरों पर आधारित नहीं है, समस्या को बहुत कम करती है। यह समस्या को पूरी तरह से हल नहीं करता है, लेकिन यह मेरे लिए कम से कम पर्याप्त है।
सेंटास्टर -
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.