समान समान और गैर-कॉन्स्टेबल सदस्य कार्यों के बीच मैं कोड दोहराव कैसे निकालूं?


242

मान लें कि मेरे पास निम्नलिखित है 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ब्रेसिज़ के अंदर समान कोड होते हैं। यह डुप्लिकेट कोड है और जटिल तर्क के साथ लंबे कार्यों के लिए रखरखाव की समस्या पैदा कर सकता है

क्या इस कोड के दोहराव से बचने का कोई तरीका है?


इस उदाहरण में, मैं कास्ट मामले में एक मान लौटाता हूं ताकि आप नीचे रीफैक्टरिंग नहीं कर सकें। int Z () const {वापसी z; }
मैट प्राइस

1
मौलिक प्रकारों के लिए, आप बिल्कुल सही हैं! मेरा पहला उदाहरण बहुत अच्छा नहीं था। मान लीजिए कि हम इसके बजाय कुछ वर्ग उदाहरण लौटा रहे हैं। (मैंने इसे प्रतिबिंबित करने के लिए सवाल अपडेट किया।)
केविन

जवाबों:


189

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

वैकल्पिक शब्द

यहाँ है मेयर्स सॉल्यूशन (सरलीकृत):

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

दो कास्ट और फ़ंक्शन कॉल बदसूरत हो सकते हैं लेकिन यह सही है। मेयर्स की गहन व्याख्या है कि क्यों।


45
स्कॉट मेयर्स :-)
स्टीव जेसप

11
witkamp सही है कि सामान्य तौर पर const_cast का उपयोग करना बुरा है। यह एक विशिष्ट मामला है जहां यह नहीं है, जैसा कि मेयर्स बताते हैं। @ एडम: रोम => कास्ट ठीक है। const == ROM स्पष्ट रूप से बकवास है क्योंकि कोई भी व्यक्ति const-const को willy-nilly नहीं कर सकता है: यह किसी चीज को संशोधित नहीं करने के लिए चुनने के बराबर है।
स्टीव जेसप 24:08

44
सामान्य तौर पर मैं स्थिर जोड़ने के लिए const_cast के बजाय const_cast का उपयोग करने का सुझाव दूंगा क्योंकि यह आपको गलती से प्रकार बदलने से रोकता है।
ग्रेग रोजर्स

6
@HelloGoodbye: मैं मेयर्स एक मान लिया गया लगता है थोड़ी वर्ग इंटरफेस के डिजाइनर से बुद्धि की। यदि कोई get()constऐसी चीज़ लौटाता है जिसे एक कांस्टेबल ऑब्जेक्ट के रूप में परिभाषित किया गया था, तो वहाँ बिल्कुल भी नॉन-कास्ट संस्करण नहीं होना चाहिए get()। वास्तव में इस पर मेरी सोच समय के साथ बदल गई है: डुप्लिकेट से बचने और कंपाइलर-चेक किए गए कांस्ट-शुद्धता पाने के लिए टेम्पलेट समाधान एकमात्र तरीका है , इसलिए व्यक्तिगत रूप से मैं अब const_castडुप्लिकेट कोड से बचने के लिए एक का उपयोग नहीं करूंगा , मैं डाल के बीच चयन करूंगा। किसी फंक्शन टेंप्लेट में डुप्लिकेट कोड या फिर उसे छोड़ दिया गया।
स्टीव जेसोप

7
निम्नलिखित दो टेम्पलेट इस समाधान की पठनीयता के साथ बहुत मदद करते हैं: template<typename T> const T& constant(T& _) { return const_cast<const T&>(_); }और template<typename T> T& variable(const T& _) { return const_cast<T&>(_); }। फिर आप कर सकते हैं:return variable(constant(*this).get());
केसी रोडरमोर

64

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

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 ++ मानक राज्यों में अपरिभाषित व्यवहार का परिणाम होगा।


3
वाह ... यह भयानक है। आपने अभी कोड की मात्रा बढ़ाई है, स्पष्टता कम की है, और दो स्टिंकिन 'const_cast <> s जोड़े हैं । शायद आपके मन में एक उदाहरण है जहाँ यह वास्तव में समझ में आता है?
शोग

14
अरे यह डिंग मत करो !, यह बदसूरत हो सकता है, लेकिन स्कॉट मेयर्स के अनुसार, यह (लगभग) सही तरीका है। देखें प्रभावी सी ++ , 3 डी एड, तो शीर्षक "स्थिरांक और गैर लागत सदस्य कार्यों में दोहराव से बचना तहत आइटम 3।
jwfearn

17
जबकि मैं समझता हूं कि समाधान बदसूरत हो सकता है, कल्पना करें कि कोड जो निर्धारित करता है कि क्या वापस लौटना है 50 रेखाएं लंबी हैं। तब दोहराव अत्यधिक अवांछनीय है - खासकर जब आपको कोड को फिर से फैक्टर करना पड़ता है। मैंने अपने करियर में कई बार इसका सामना किया है।
केविन

8
इस और मेयर्स के बीच अंतर यह है कि मेयर्स के पास static_cast <const X &> (* this) है। const_cast कास्ट हटाने के लिए है, इसे जोड़ने के लिए नहीं।
22:17 पर स्टीव जेसोप

8
@VioletGiraffe हम जानते हैं कि ऑब्जेक्ट को मूल रूप से const नहीं बनाया गया था, क्योंकि यह एक नॉन-कॉस्ट ऑब्जेक्ट का एक नॉन-कॉस्ट मेंबर है, जिसे हम जानते हैं क्योंकि हम उक्त ऑब्जेक्ट के एक नॉन-कॉस्ट मेथड में हैं। संकलक इस निष्कर्ष को नहीं बनाता है, यह एक रूढ़िवादी नियम का पालन करता है। आपको क्या लगता है कि const_cast मौजूद है, अगर इस तरह की स्थिति के लिए नहीं?
कैलथ

47

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

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

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

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

यदि आप पूर्ण कटौती मार्ग पर जाना चाहते हैं तो एक सहायक कार्य करके इसे पूरा किया जा सकता है

template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
    return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
    return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
    return value;
}
template<typename T>
void as_mutable(T const &&) = delete;

अब आप भी गड़बड़ नहीं कर सकते volatile, और उपयोग की तरह दिखता है

decltype(auto) f() const {
    return something_complicated();
}
decltype(auto) f() {
    return as_mutable(std::as_const(*this).f());
}

ध्यान दें कि "r_mutable" कॉन्स्टेबल रैवले ओवरलोड के साथ हटा दिया गया है (जो आमतौर पर बेहतर है) अंतिम उदाहरण को काम करने से रोकता है यदि f()रिटर्न के Tबजाय T&
मैक्स Truxa

1
@MaxTruxa: हाँ, और यह एक अच्छी बात है। यदि यह सिर्फ संकलित किया जाता है, तो हमारे पास एक झूलने वाला संदर्भ होगा। ऐसे मामले में जहां हम f()वापसी करते हैं T, हम दो अधिभार नहीं चाहते हैं, constअकेले संस्करण पर्याप्त है।
डेविड स्टोन

बहुत सच है, मैं कल अपने पूर्ण-मस्तिष्क मस्तिष्क के लिए माफी माँगता हूँ, मुझे पता नहीं था कि मैं उस टिप्पणी को लिखने के बारे में क्या सोच रहा था। मैं एक कॉन्स्टेबल / म्यूटेबल गटर जोड़ी को देख रहा था, जो वापस लौट रही थी shared_ptr। तो मुझे वास्तव में as_mutable_ptrजिस चीज़ की ज़रूरत थी वह कुछ ऐसा था जो as_mutableऊपर से लगभग समान दिखता है, सिवाय इसके कि यह लेता है और इसके बजाय shared_ptrउपयोग करता है और वापस लौटता है । std::const_pointer_castconst_cast
मैक्स Truxa

1
यदि कोई विधि वापस आती है T const*तो यह T const* const&&बाध्यकारी होने के बजाय T const* const&(कम से कम मेरे परीक्षण में) ऐसा करने के लिए बाध्य होगी । मुझे T const*एक पॉइंटर जोड़ना था क्योंकि एक पॉइंटर को वापस करने के तरीकों के लिए तर्क प्रकार।
बंदर ०५०६

2
@ Monkey0506: मैंने बिंदुओं के साथ-साथ संदर्भों का समर्थन करने के लिए अपना जवाब अपडेट किया है
डेविड स्टोन

34

मुझे लगता है कि स्कॉट मेयर्स के समाधान को एक अस्थायी सहायक फ़ंक्शन का उपयोग करके C ++ 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);
   }
};

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


3
यह शर्म की बात है कि हमें std::remove_bottom_constसाथ नहीं जाना है std::remove_const
3

मुझे यह समाधान पसंद नहीं है क्योंकि यह अभी भी एंबेड करता है const_cast। आप getElementस्वयं एक टेम्प्लेट बना सकते हैं , और यदि आवश्यक हो, तो एस या एस की mpl::conditionalतरह टाइप के लक्षण का उपयोग अंदर कर सकते हैं। वास्तविक समस्या यह है कि हस्ताक्षर के इस भाग को गतिविभाजित नहीं किया जा सकता है तो विधि का एक कॉस्ट संस्करण कैसे उत्पन्न किया जाए? iteratorconstiterator
v.oddou

2
@ v.oddou: std::remove_const<int const&>है int const &(निकालें उच्च-स्तरीय constयोग्यता), इसलिए की जिमनास्टिक NonConst<T>इस जवाब में। Putative std::remove_bottom_constनीचे-स्तर की constयोग्यता को हटा सकता है , और ठीक वही NonConst<T>करता है जो यहाँ करता है: std::remove_bottom_const<int const&>::type=> int&
TBBle

4
यह समाधान अच्छी तरह से काम नहीं करता है, अगर getElementअतिभारित है। तब फ़ंक्शन पॉइंटर को स्पष्ट रूप से टेम्पलेट पैरामीटर दिए बिना हल नहीं किया जा सकता है। क्यों?
जॉन

1
आपको C ++ 11 सही फ़ॉरवर्डिंग का उपयोग करने के लिए आपको उत्तर देने की आवश्यकता है: likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }पूर्ण: gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83
ShaulF

22

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

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); }
};

निजी पद्धति के पास अवांछनीय संपत्ति है कि यह एक गैर-ज़ेड Z & एक कास्ट इंस्टेंस के लिए लौटाता है, यही कारण है कि यह निजी है। निजी विधियाँ बाहरी इंटरफ़ेस के आवेगों को तोड़ सकती हैं (इस मामले में वांछित अपरिवर्तनीय है "एक कॉस्ट ऑब्जेक्ट को इसके माध्यम से प्राप्त वस्तुओं के माध्यम से संशोधित नहीं किया जा सकता है, जिसमें यह है" - "।

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

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

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

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


3
यह अभी भी समस्या है कि आप जिस चीज़ को वापस करते हैं वह एक्स के लगातार उदाहरण के लिए स्थिर हो सकता है। उस स्थिति में, आपको अभी भी _getZ (...) में एक const_cast की आवश्यकता है। यदि बाद के डेवलपर्स द्वारा दुरुपयोग किया जाता है, तो यह अभी भी यूबी को जन्म दे सकता है। यदि जो वस्तु वापस की जा रही है, वह 'परस्पर' है, तो यह एक अच्छा उपाय है।
केविन

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

13
-1: यह कई स्थितियों में काम नहीं करता है। क्या होगा अगर somethingमें _getZ()एक उदाहरण चर समारोह है? संकलक (या कम से कम कुछ संकलक) शिकायत करेंगे कि चूंकि _getZ()कॉन्स्ट है, किसी भी उदाहरण के भीतर संदर्भित चर भी कॉस्ट है। तो somethingफिर कास्ट (यह प्रकार का const Z&) होगा और इसे परिवर्तित नहीं किया जा सकता है Z&। मेरे (आमतौर पर कुछ हद तक सीमित) अनुभव में, ज्यादातर समय somethingइस तरह के मामलों में एक उदाहरण चर होता है।
गुरुत्व

2
@ ग्रेविटीबिंगर: फिर "कुछ" को शामिल करने की आवश्यकता है const_cast। यह कोड ऑब्जेक्ट के लिए एक स्थान-धारक होने का इरादा था, जो कि कास्ट ऑब्जेक्ट से एक गैर-कॉन्स्टेंट रिटर्न प्राप्त करने के लिए आवश्यक था, न कि डुप्लिकेटेड गेट्टर में क्या होता है के लिए एक जगह-धारक के रूप में। तो "कुछ" केवल एक उदाहरण चर नहीं है।
स्टीव जेसोप

2
समझा। यह वास्तव में तकनीक की उपयोगिता को कम करता है, हालांकि। मैं नीचे को हटा दूँगा, लेकिन SO मुझे नहीं जाने देगा।
गुरुत्व

22

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

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 इसके अंदर चर है।

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


4
ठीक है, चलो सरल तथ्य के साथ चलते हैं कि आपने अधिक बॉयलरप्लेट जोड़ा। यदि कुछ भी हो, तो इसका उपयोग एक उदाहरण के रूप में किया जाना चाहिए कि भाषा को रिटर्न प्रकार के साथ-साथ फ़ंक्शन क्वालीफायर को संशोधित करने के तरीके की आवश्यकता है auto get(std::size_t i) -> auto(const), auto(&&)। क्यों '&&'? आह, तो मैं कह सकता हूँ:auto foo() -> auto(const), auto(&&) = delete;
kfsone

gd1: वास्तव में मेरे मन में क्या था। @kfsone और वास्तव में मैं भी निष्कर्ष निकाला।
v.oddou

1
@kfsone सिंटैक्स में thisकीवर्ड शामिल होना चाहिए । मेरा सुझाव है template< typename T > auto myfunction(T this, t args) -> decltype(ident)कि इस खोजशब्द को निहित वस्तु उदाहरण तर्क के रूप में पहचाना जाएगा और संकलक को यह पहचानने देगा कि myfunction सदस्य है या TTकॉल साइट पर ऑटो कटौती की जाएगी, जो हमेशा क्लास का प्रकार होगा, लेकिन मुफ्त सीवी योग्यता के साथ।
v.oddou

2
उस समाधान का भी लाभ (बनाम const_castएक) है जो उसे वापस करने की अनुमति देता है iteratorऔर const_iterator
11:42 पर Jarod42

1
यदि कार्यान्वयन को cpp फ़ाइल में स्थानांतरित किया जाता है (और जैसा कि डुप्लिकेट नहीं करने की विधि तुच्छ नहीं होनी चाहिए, तो शायद यह मामला होगा), staticवर्ग गुंजाइश के बजाय फ़ाइल स्कोप पर किया जा सकता है। :-)
Jarod42

8

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

.h फ़ाइल:

#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 () के ऊपर सार्वजनिक विधियों का उपयोग करके X के सदस्यों को पकड़ना होगा (जिनमें से हमेशा एक होने की आवश्यकता है) const और नॉन-कास्ट संस्करण) या आप इस फ़ंक्शन को मित्र बना सकते हैं। लेकिन मुझे दोस्त पसंद नहीं हैं।

[संपादित करें: परीक्षण के दौरान हटाए गए cstdio के अनावश्यक लिंक शामिल हैं।]


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

आह हां अच्छा विचार! मुझे हेडर में दिखाई देने वाला टेम्प्लेट सामान पसंद नहीं है, लेकिन अगर यहाँ से यह संभावित रूप से लागू होता है तो यह बहुत सरल है शायद यह इसके लायक है।
एंडी बालाम

+ 1 इस समाधान के लिए, जो किसी भी कोड को डुप्लिकेट नहीं करता है, और न ही किसी भी बदसूरत का उपयोग करता है const_cast(जो गलती से उस चीज़ को कैनस्ट करने के लिए इस्तेमाल किया जा सकता है जो वास्तव में उस चीज़ के लिए कॉन्स्टेबल होना चाहिए जो ऐसा नहीं है)।
हैलोगूडीबाई

आजकल इसे टेम्पलेट के लिए एक कम रिटर्न प्रकार के साथ सरलीकृत किया जा सकता है (विशेष रूप से तब से जब यह कम हो जाता है कि सदस्य मामले में कक्षा में नकल की जानी चाहिए)।
डेविस हेरिंग

3

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


अपरिभाषित व्यवहार से बचने के लिए आपको अभी भी एक const_cast की आवश्यकता है। मार्टिन यॉर्क द्वारा उत्तर और मेरी टिप्पणी देखें।
केविन

1
केविन, मार्टिन ने उत्तर क्या दिया
पीटर निम्मो

2

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

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

};

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


1
लेकिन फिर आपको बैकस्लैश (हमेशा की तरह मल्टीलाइन मैक्रों) के साथ सावधान रहना होगा और इसके अलावा आप सबसे अधिक (यदि सभी नहीं) संपादकों में वाक्य रचना हाइलाइटिंग खो देते हैं।
रुस्लान

2

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

मैंने एक मैक्रो लिखा है FROM_CONST_OVERLOAD()जिसे कॉन्स्ट फ़ंक्शन को लागू करने के लिए नॉन-कॉस्ट फ़ंक्शन में रखा जा सकता है।

उदाहरण का उपयोग:

class MyClass
{
private:
    std::vector<std::string> data = {"str", "x"};

public:
    // Works for references
    const std::string& GetRef(std::size_t index) const
    {
        return data[index];
    }

    std::string& GetRef(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetRef(index) );
    }


    // Works for pointers
    const std::string* GetPtr(std::size_t index) const
    {
        return &data[index];
    }

    std::string* GetPtr(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetPtr(index) );
    }
};

सरल और पुन: प्रयोज्य कार्यान्वयन:

template <typename T>
T& WithoutConst(const T& ref)
{
    return const_cast<T&>(ref);
}

template <typename T>
T* WithoutConst(const T* ptr)
{
    return const_cast<T*>(ptr);
}

template <typename T>
const T* WithConst(T* ptr)
{
    return ptr;
}

#define FROM_CONST_OVERLOAD(FunctionCall) \
  WithoutConst(WithConst(this)->FunctionCall)

स्पष्टीकरण:

जैसा कि कई उत्तरों में पोस्ट किया गया है, गैर-सदस्य सदस्य फ़ंक्शन में कोड दोहराव से बचने के लिए विशिष्ट पैटर्न यह है:

return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );

इस बायलरप्लेट के बहुत से प्रकार का उपयोग करने से बचा जा सकता है। सबसे पहले, const_castइनकैप्सुलेट किया जा सकता है WithoutConst(), जो अपने तर्क के प्रकार को संक्रमित करता है और कॉन्स्ट-क्वालीफायर को हटा देता है। दूसरा, एक समान दृष्टिकोण का उपयोग पॉइंटर WithConst()को कसने के लिए किया जा सकता है this, जो कॉन्स्ट-ओवरलोड विधि को कॉल करने में सक्षम बनाता है।

बाकी एक साधारण मैक्रो है जो कॉल को सही ढंग से योग्य के साथ उपसर्ग करता है this->और परिणाम से कास्ट हटाता है। चूंकि मैक्रो में उपयोग की जाने वाली अभिव्यक्ति लगभग हमेशा 1: 1 अग्रेषित तर्कों के साथ एक साधारण फ़ंक्शन कॉल होती है, मैक्रोज़ की कमियां जैसे कि कई मूल्यांकन में किक नहीं होती है। दीर्घवृत्त।__VA_ARGS__ भी इस्तेमाल किया जा सकता है, लेकिन कॉमा (क्योंकि) की आवश्यकता नहीं होनी चाहिए। तर्क विभाजक) कोष्ठक के भीतर होते हैं।

इस दृष्टिकोण के कई लाभ हैं:

  • न्यूनतम और प्राकृतिक सिंटैक्स - बस कॉल को अंदर लपेटें FROM_CONST_OVERLOAD( )
  • कोई अतिरिक्त सदस्य फ़ंक्शन की आवश्यकता नहीं है
  • C ++ 98 के साथ संगत
  • सरल कार्यान्वयन, कोई टेम्पलेट मेटाप्रोग्रामिंग और शून्य निर्भरता नहीं
  • एक्सटेंसिबल: अन्य स्थिरांक संबंधों जोड़ा जा सकता है (जैसे const_iterator, std::shared_ptr<const T>, आदि)। इसके WithoutConst()लिए, संबंधित प्रकारों के लिए बस ओवरलोड करें ।

सीमाएँ: यह समाधान उन परिदृश्यों के लिए अनुकूलित है जहाँ नॉन-कॉन्स्टल अधिभार बिल्कुल ओवरलोड की तरह ही कर रहा है, ताकि तर्क 1: 1 को अग्रेषित किया जा सके। यदि आपका तर्क अलग है और आप कास्ट संस्करण को कॉल नहीं कर रहे हैं this->Method(args), तो आप अन्य तरीकों पर विचार कर सकते हैं।


2

उन (मेरे जैसे) के लिए जो

  • उपयोग c ++ 17
  • बॉयलरप्लेट / पुनरावृत्ति और की कम से कम राशि जोड़ना चाहते हैं
  • मैक्रों का उपयोग करने में कोई आपत्ति नहीं है (मेटा-क्लास के लिए प्रतीक्षा करते समय ...),

यहाँ एक और लेना है:

#include <utility>
#include <type_traits>

template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};

#define NON_CONST(func)                                                     \
    template <typename... T> auto func(T&&... a)                            \
        -> typename NonConst<decltype(func(std::forward<T>(a)...))>::type   \
    {                                                                       \
        return const_cast<decltype(func(std::forward<T>(a)...))>(           \
            std::as_const(*this).func(std::forward<T>(a)...));              \
    }

यह मूल रूप से @Pait, @DavidStone और @ sh1 ( EDIT : और @cdhowie से सुधार) के उत्तरों का मिश्रण है । यह तालिका में जो जोड़ता है वह यह है कि आप केवल एक अतिरिक्त पंक्ति कोड के साथ दूर हो जाते हैं जो केवल फ़ंक्शन को नाम देता है (लेकिन कोई तर्क या वापसी प्रकार दोहराव नहीं):

class X
{
    const Z& get(size_t index) const { ... }
    NON_CONST(get)
};

नोट: gcc 8.1, clang-5 और इससे पहले के संकलनों के लिए इसे संकलित करने में विफल रहता है और साथ ही MSVC-19 खुश हैं ( कंपाइलर एक्सप्लोरर के अनुसार )।


इसने मेरे लिए सीधे काम किया। यह एक महान जवाब है, धन्यवाद!
शॉर्ट

क्या यह सुनिश्चित करने के लिए तर्कों decltype()का उपयोग नहीं किया जाना चाहिए std::forwardकि हम उस मामले में सही रिटर्न प्रकार का उपयोग कर रहे हैं जहां हमारे पास ओवरलोड हैं get()जो विभिन्न प्रकार के संदर्भ लेते हैं?
cdhowie

@ LCDhowie क्या आप एक उदाहरण प्रदान कर सकते हैं?
axxel

@axxel यह नरक के रूप में वंचित है, लेकिन यहां आप जाते हैंNON_CONSTमैक्रो deduces वापसी प्रकार गलत तरीके से और const_castमें अग्रेषण की कमी के कारण गलत प्रकार के एस decltype(func(a...))प्रकार के। उन्हें decltype(func(std::forward<T>(a)...)) इस के साथ बदल रहा है । (सिर्फ एक लिंकर त्रुटि है क्योंकि मैंने कभी भी घोषित X::getओवरलोड को परिभाषित नहीं किया है ।)
cdhowie

1
धन्यवाद @ LCDhowie, मैंने वास्तव में नॉन- कॉन्स्टल
axxel

1

यहां टेम्प्लेट स्टैटिक हेल्पर फंक्शन का C ++ 17 वर्जन है, जिसमें वैकल्पिक SFINAE टेस्ट है।

#include <type_traits>

#define REQUIRES(...)         class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B)   REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )

class Foobar {
private:
    int something;

    template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
    static auto& _getSomething(FOOBAR& self, int index) {
        // big, non-trivial chunk of code...
        return self.something;
    }

public:
    auto& getSomething(int index)       { return _getSomething(*this, index); }
    auto& getSomething(int index) const { return _getSomething(*this, index); }
};

पूर्ण संस्करण: https://godbolt.org/z/mMK4r3


1

मैं एक मैक्रो के साथ आया जो स्वचालित रूप से कॉन्स्ट / नॉन-कास्ट फ़ंक्शन के जोड़े उत्पन्न करता है।

class A
{
    int x;    
  public:
    MAYBE_CONST(
        CV int &GetX() CV {return x;}
        CV int &GetY() CV {return y;}
    )

    //   Equivalent to:
    // int &GetX() {return x;}
    // int &GetY() {return y;}
    // const int &GetX() const {return x;}
    // const int &GetY() const {return y;}
};

कार्यान्वयन के लिए उत्तर का अंत देखें।

के तर्क की MAYBE_CONSTनकल की जाती है। पहली प्रति में, CVकुछ भी नहीं के साथ बदल दिया जाता है; और दूसरी प्रति में इसे बदल दिया जाता है const

CVमैक्रो तर्क में कितनी बार दिखाई दे सकती है, इसकी कोई सीमा नहीं है ।

हालांकि थोड़ी असुविधा है। यदि CVकोष्ठक के अंदर प्रकट होता है, तो कोष्ठक की इस जोड़ी के साथ उपसर्ग होना चाहिए CV_IN:

// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )

// Works, expands to
//         int &foo(      int &);
//   const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )

कार्यान्वयन:

#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,

#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )

#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq

#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__

#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)

#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)

प्री-सी ++ 20 कार्यान्वयन जो समर्थन नहीं करता है CV_IN:

#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((

#define IMPL_MC(seq) \
    IMPL_MC_end(IMPL_MC_a seq) \
    IMPL_MC_end(IMPL_MC_const_0 seq)

#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end

#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end

#define IMPL_MC_const_0(elem)       IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end

0

आमतौर पर, सदस्य फ़ंक्शंस जिनके लिए आपको कॉन्स्ट और नॉन-कॉस्ट संस्करण की आवश्यकता होती है वे गेटर्स और सेटर हैं। ज्यादातर समय वे वन-लाइनर्स होते हैं इसलिए कोड डुप्लिकेट एक मुद्दा नहीं है।


2
यह ज्यादातर समय सच हो सकता है। लेकिन अपवाद हैं।
केविन

1
वैसे भी, एक
कास्ट

मेरा मतलब था कि नॉन-कास्ट गेटटर प्रभावी रूप से एक सेटर है। :)
दीमा

0

मैंने एक दोस्त के लिए ऐसा किया, जिसने इसके इस्तेमाल को सही ठहराया 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;
}

0

मैं इस तरह एक निजी सहायक स्थैतिक समारोह टेम्पलेट का सुझाव देता हूं:

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

    // ReturnType is explicitly 'Z&' or 'const Z&'
    // ThisType is deduced to be 'X' or 'const X'
    template <typename ReturnType, typename ThisType>
    static ReturnType Z_impl(ThisType& self, size_t index)
    {
        // massive amounts of code for validating index
        ReturnType ret = self.vecZ[index];
        // even more code for determining, blah, blah...
        return ret;
    }

public:
    Z& Z(size_t index)
    {
        return Z_impl<Z&>(*this, index);
    }
    const Z& Z(size_t index) const
    {
        return Z_impl<const Z&>(*this, index);
    }
};

-1

यह DDJ लेख टेम्पलेट विशेषज्ञता का उपयोग करके एक रास्ता दिखाता है जिसके लिए आपको const_cast का उपयोग करने की आवश्यकता नहीं है। इस तरह के एक सरल कार्य के लिए वास्तव में इसकी आवश्यकता नहीं है।

बढ़ावा देना :: any_cast (एक बिंदु पर, यह किसी भी अधिक नहीं है) नकल से बचने के लिए नॉन-कास्ट संस्करण को कॉल करने वाले कॉन्स्ट संस्करण से एक const_cast का उपयोग करता है। आप नॉन-कॉस्ट संस्करण पर कॉन्स्टेंट शब्दार्थ नहीं लगा सकते, हालांकि आपको इससे बहुत सावधान रहना होगा।

अंत में कुछ कोड दोहराव है ठीक जब तक दो स्निपेट एक दूसरे के ऊपर पर सीधे कर रहे हैं।


DDJ लेख पुनरावृत्तियों का संदर्भ देता है - जो प्रश्न के लिए प्रासंगिक नहीं है। कॉन्स्टेंट-इटरेटर निरंतर डेटा नहीं हैं - वे पुनरावृत्त हैं जो निरंतर डेटा को इंगित करते हैं।
केविन

-1

प्रदान किए गए समाधान jwfearn और kevin में जोड़ने के लिए, जब फ़ंक्शन साझा किया जाता है, तो यहां संगत समाधान होता है:

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

-1

मुझे जो मिल रहा था, वह नहीं मिला, इसलिए मैंने अपने खुद के एक जोड़े को रोल किया ...

यह एक छोटी सी चिंता है, लेकिन एक ही नाम (और वापसी प्रकार) के कई अतिभारित तरीकों को एक साथ संभालने का फायदा है:

struct C {
  int x[10];

  int const* getp() const { return x; }
  int const* getp(int i) const { return &x[i]; }
  int const* getp(int* p) const { return &x[*p]; }

  int const& getr() const { return x[0]; }
  int const& getr(int i) const { return x[i]; }
  int const& getr(int* p) const { return x[*p]; }

  template<typename... Ts>
  auto* getp(Ts... args) {
    auto const* p = this;
    return const_cast<int*>(p->getp(args...));
  }

  template<typename... Ts>
  auto& getr(Ts... args) {
    auto const* p = this;
    return const_cast<int&>(p->getr(args...));
  }
};

यदि आपके पास constप्रति नाम केवल एक विधि है, लेकिन फिर भी नकल करने के लिए बहुत सारे तरीके हैं, तो आप इसे पसंद कर सकते हैं:

  template<typename T, typename... Ts>
  auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) {
    return const_cast<T*>((this->*f)(args...));
  }

  int* getp_i(int i) { return pwrap(&C::getp_i, i); }
  int* getp_p(int* p) { return pwrap(&C::getp_p, p); }

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

  template<typename... Ts>
  auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); }

लेकिन constविधि के संदर्भ संदर्भ , खाके से स्पष्ट रूप से उप-मूल्य तर्क के खिलाफ मेल करने में विफल होते हैं और यह टूट जाता है। यकीन नहीं है कि क्यों। यहाँ क्यों है

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.