c++ - मैं समान कॉन्स और गैर-कॉन्स सदस्य फ़ंक्शंस के बीच कोड डुप्लिकेशन कैसे हटा सकता हूं?




class const (10)

मान लें कि मेरे पास निम्न class X जहां मैं किसी आंतरिक सदस्य तक पहुंच वापस करना चाहता हूं:

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    Z& Z(size_t index)
    {
        // massive amounts of code for validating index

        Z& ret = vecZ[index];

        // even more code for determining that the Z instance
        // at index is *exactly* the right sort of Z (a process
        // which involves calculating leap years in which
        // religious holidays fall on Tuesdays for
        // the next thousand years or so)

        return ret;
    }
    const Z& Z(size_t index) const
    {
        // identical to non-const X::Z(), except printed in
        // a lighter shade of gray since
        // we're running low on toner by this point
    }
};

दो सदस्य कार्य X::Z() और X::Z() const में ब्रेसिज़ के अंदर समान कोड है। यह डुप्लिकेट कोड है और जटिल तर्क के साथ लंबे कार्यों के लिए रखरखाव की समस्याएं पैदा कर सकता है

क्या इस कोड डुप्लिकेशन से बचने का कोई तरीका है?


अच्छा सवाल और अच्छे जवाब। मेरे पास एक और समाधान है, जो कि कोई कास्ट नहीं करता है:

class X {

private:

    std::vector<Z> v;

    template<typename InstanceType>
    static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
        // massive amounts of code for validating index
        // the instance variable has to be used to access class members
        return instance.v[i];
    }

public:

    const Z& get(std::size_t i) const {
        return get(*this, i);
    }

    Z& get(std::size_t i) {
        return get(*this, i);
    }

};

हालांकि, इसमें एक स्थिर सदस्य और इसके अंदर instance चर का उपयोग करने की आवश्यकता की आवश्यकता है।

मैंने इस समाधान के सभी संभावित (नकारात्मक) प्रभावों पर विचार नहीं किया। अगर कोई है तो कृपया मुझे बताएं।


आप टेम्पलेट्स के साथ इसे हल भी कर सकते हैं। यह समाधान थोड़ा बदसूरत है (लेकिन कुरूपता .cpp फ़ाइल में छिपी हुई है) लेकिन यह स्थिरता की कंपाइलर जांच प्रदान करता है, और कोई कोड डुप्लिकेशन नहीं देता है।

एच फाइल:

#include <vector>

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

.cpp फ़ाइल:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

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

[संपादित करें: परीक्षण के दौरान जोड़ा गया cstdio के unneeded शामिल हटा दिया।]


एक विस्तृत स्पष्टीकरण के लिए, कृपया पी पर "शीर्षक और नॉन- const सदस्य फ़ंक्शन में डुप्लिकेशन से बचें" शीर्षक देखें। 23, आइटम 3 में "जब भी संभव हो," जब भी संभव हो, " प्रभावी सी ++ में , स्कॉट मेयर्स द्वारा 3 डी एड, आईएसबीएन -13: 9 780321334879।

मेयर्स का समाधान यहां है (सरलीकृत):

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast<char &>(static_cast<const C &>(*this).get());
  }
  char c;
};

दो कास्ट और फ़ंक्शन कॉल बदसूरत हो सकती है लेकिन यह सही है। मेयर्स का एक संपूर्ण स्पष्टीकरण क्यों है।


क्या यह प्रीप्रोसेसर का उपयोग करने के लिए धोखा दे रहा है?

struct A {

    #define GETTER_CORE_CODE       \
    /* line 1 of getter code */    \
    /* line 2 of getter code */    \
    /* .....etc............. */    \
    /* line n of getter code */       

    // ^ NOTE: line continuation char '\' on all lines but the last

   B& get() {
        GETTER_CORE_CODE
   }

   const B& get() const {
        GETTER_CORE_CODE
   }

   #undef GETTER_CORE_CODE

};

यह टेम्पलेट्स या कास्ट्स के रूप में फैंसी नहीं है, लेकिन यह आपके इरादे को बनाता है ("ये दो कार्य समान हैं") बहुत स्पष्ट है।


मुझे लगता है कि एक टेम्पेट हेल्पर फ़ंक्शन का उपयोग करके स्कॉट मेयर्स का समाधान सी ++ 11 में सुधार किया जा सकता है। इससे इरादा अधिक स्पष्ट हो जाता है और कई अन्य गेटर्स के लिए इसका पुन: उपयोग किया जा सकता है।

template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference

template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs&&... args) {
      return const_cast<typename NonConst<TConstReturn>::type>(
         (obj->*memFun)(std::forward<TArgs>(args)...));
}

इस सहायक समारोह का उपयोग निम्न तरीके से किया जा सकता है।

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

पहला तर्क हमेशा यह सूचक है। दूसरा कॉल करने के लिए सदस्य समारोह के सूचक है। इसके बाद अतिरिक्त तर्कों की मनमानी राशि पारित की जा सकती है ताकि उन्हें कार्य में अग्रेषित किया जा सके। विविधता टेम्पलेट्स के कारण इसे सी ++ 11 की आवश्यकता है।


मेयर्स की तुलना में थोड़ा अधिक वर्बोज़, लेकिन मैं यह कर सकता हूं:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

निजी विधि में अवांछित संपत्ति है जो यह एक गैर-कॉन्स जेड और एक कॉन्स इंस्टेंस के लिए देता है, यही कारण है कि यह निजी है। निजी विधियां बाहरी इंटरफ़ेस के इनवेरिएंट को तोड़ सकती हैं (इस मामले में वांछित invariant "एक कॉन्स ऑब्जेक्ट को इसके द्वारा प्राप्त संदर्भों के माध्यम से संशोधित संदर्भों के माध्यम से संशोधित नहीं किया जा सकता है")।

ध्यान दें कि टिप्पणियां पैटर्न का हिस्सा हैं - _getZ का इंटरफ़ेस निर्दिष्ट करता है कि इसे कॉल करने के लिए कभी भी वैध नहीं है (स्पष्ट रूप से एक्सेसर्स से अलग): वैसे भी ऐसा करने के लिए कोई कल्पनीय लाभ नहीं है, क्योंकि यह टाइप करने के लिए 1 और वर्ण है और नहीं परिणामस्वरूप छोटे या तेज कोड। विधि को कॉल करना एक कॉन्स्ट_कास्ट वाले एक्सेसर्स में से किसी एक को कॉल करने के बराबर है, और आप इसे भी नहीं करना चाहेंगे। यदि आप त्रुटियों को स्पष्ट करने के बारे में चिंतित हैं (और यह एक उचित लक्ष्य है), तो _getZ के बजाय इसे const_cast_getZ पर कॉल करें।

वैसे, मैं मेयर्स के समाधान की सराहना करता हूं। मेरे पास इसके लिए कोई दार्शनिक आपत्ति नहीं है। व्यक्तिगत रूप से, हालांकि, मैं नियंत्रित पुनरावृत्ति का एक छोटा सा हिस्सा पसंद करता हूं, और एक निजी विधि जिसे केवल कुछ कसकर नियंत्रित परिस्थितियों में ही जाना चाहिए, जो कि लाइन शोर की तरह दिखता है। अपने जहर उठाओ और इसके साथ छड़ी।

[संपादित करें: केविन ने सही ढंग से इंगित किया है कि _getZ एक और विधि (जेनरजेड कहें) को कॉल करना चाह सकता है जो कि GetZ है उसी तरह से विशेष रूप से विशिष्ट है। इस मामले में, _getZ एक कॉन्स जेड देखेंगे और इसे वापस करने से पहले इसे const_cast करना होगा। यह अभी भी सुरक्षित है, क्योंकि बॉयलरप्लेट एक्सेसर सबकुछ दिखाता है, लेकिन यह स्पष्ट रूप से स्पष्ट नहीं है कि यह सुरक्षित है। इसके अलावा, यदि आप ऐसा करते हैं और उसके बाद जेनरजेड को हमेशा कॉन्स लौटने के लिए बदलते हैं, तो आपको हमेशा गेज को वापस करने के लिए getZ को बदलने की आवश्यकता होती है, लेकिन कंपाइलर आपको यह नहीं बताएगा कि आप करते हैं।

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


मैंने यह एक ऐसे दोस्त के लिए किया जिसने const_cast के उपयोग को उचित रूप से उचित ठहराया ... इसके बारे में नहीं जानते, शायद मैंने ऐसा कुछ किया होगा (वास्तव में सुरुचिपूर्ण नहीं):

#include <iostream>

class MyClass
{

public:

    int getI()
    {
        std::cout << "non-const getter" << std::endl;
        return privateGetI<MyClass, int>(*this);
    }

    const int getI() const
    {
        std::cout << "const getter" << std::endl;
        return privateGetI<const MyClass, const int>(*this);
    }

private:

    template <class C, typename T>
    static T privateGetI(C c)
    {
        //do my stuff
        return c._i;
    }

    int _i;
};

int main()
{
    const MyClass myConstClass = MyClass();
    myConstClass.getI();

    MyClass myNonConstClass;
    myNonConstClass.getI();

    return 0;
}

लॉजिक को एक निजी विधि में कैसे ले जाया जा रहा है, और केवल गेटर्स के अंदर "संदर्भ और वापसी" सामान कर रहा है? असल में, मैं स्थिर गठबंधन समारोह के अंदर स्थैतिक और कॉन्स के बारे में काफी उलझन में रहूंगा, और मैं बेहद दुर्लभ परिस्थितियों को छोड़कर बदसूरत मानता हूं!


सी ++ 17 ने इस प्रश्न के लिए सबसे अच्छा जवाब अपडेट किया है:

T const & f() const {
    return something_complicated();
}
T & f() {
    return const_cast<T &>(std::as_const(*this).f());
}

इसमें फायदे हैं कि यह:

  • क्या स्पष्ट हो रहा है
  • न्यूनतम कोड ओवरहेड है - यह एक ही पंक्ति में फिट बैठता है
  • गलत होना मुश्किल है (केवल दुर्घटना से volatile हो सकता है, लेकिन volatile एक दुर्लभ क्वालीफायर है)

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

class X
{
   std::vector<Z> vecZ;

public:
   const Z& Z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& Z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast<Z&>( static_cast<const X&>(*this).Z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast<Z&>( constMe.Z(index) );
   }
 #endif
};

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





c++-faq