tutorial - the c++ programming language




আমি কিভাবে একই গঠন এবং অ-সদস্য সদস্য ফাংশনগুলির মধ্যে কোড অনুলিপি অপসারণ করব? (11)

ধরুন আমার নিম্ন 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 ভিতরে অভিন্ন কোড রয়েছে। এটি সদৃশ কোড এবং জটিল যুক্তিযুক্ত লম্বা ফাংশনগুলির জন্য রক্ষণাবেক্ষণ সমস্যা সৃষ্টি করতে পারে

এই কোড অনুলিপি এড়াতে একটি উপায় আছে কি?


আপনি টেমপ্লেট সঙ্গে এই সমাধান করতে পারে। এই সমাধানটি সামান্য কুৎসিত (তবে কুসংস্কারটি। সিপিপি ফাইলের মধ্যে লুকানো থাকে) তবে এটি সংশ্লেষের কম্পাইলার চেকিং এবং কোনও কোড অনুলিপি সরবরাহ করে না।

.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 এবং অ-কনস্টেবল সংস্করণ) অথবা আপনি এই ফাংশন একটি বন্ধু করতে পারে। কিন্তু আমি বন্ধু পছন্দ করি না।

[সম্পাদনা: পরীক্ষার সময় unsteded অন্তর্ভুক্ত cstdio অন্তর্ভুক্ত।]


আমি একটি ব্যক্তিগত সাহায্যকারী স্ট্যাটিক ফাংশন টেমপ্লেট সুপারিশ চাই, এই মত:

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

আমি মনে করি স্কট মেয়ের্স সমাধানটি একটি টেমপ্লেট সহায়ক ফাংশন ব্যবহার করে C ++ 11 এ উন্নত করা যেতে পারে। এই অভিপ্রায় অনেক বেশি স্পষ্ট করে তোলে এবং অনেক অন্যান্য পেতে জন্য reused করা যেতে পারে।

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 প্রয়োজন।


আমি যা খুজছি তা খুঁজে পাইনি, তাই আমি আমার নিজের কয়েকটি ঘূর্ণায়মান ...

এটি একটি সামান্য শব্দকোষ, তবে একই নামের (এবং রিটার্ন টাইপ) অনেকগুলি ওভারলোড হওয়া পদ্ধতিগুলি একযোগে পরিচালনা করার সুবিধা রয়েছে:

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 পদ্ধতিতে রেফারেন্স আর্গুমেন্টগুলি টেমপ্লেটে আপাতদৃষ্টিতে বাই-মানের আর্গুমেন্টগুলির সাথে মেলে না এবং এটি বিরতি দেয়। নিশ্চিত না কেন। এখানে কেন ।


কিভাবে একটি ব্যক্তিগত পদ্ধতিতে যুক্তি সরানো সম্পর্কে, এবং শুধুমাত্র পেতে "পেতে রেফারেন্স এবং ফিরে পেতে" স্টাফ জিনিসপত্র ভিতরে? প্রকৃতপক্ষে, আমি একটি সহজ গেটার ফাংশন ভিতরে স্ট্যাটিক এবং কনস্টেস্ট সম্পর্কে মোটামুটি বিভ্রান্ত হবে, এবং অত্যন্ত বিরল পরিস্থিতিতে ছাড়া আমি যে কুৎসিত বিবেচনা করবে!


চমৎকার প্রশ্ন এবং চমৎকার উত্তর। আমি অন্য সমাধান আছে, যে কোন casts ব্যবহার করে:

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 ভেরিয়েবল ব্যবহার করার প্রয়োজন আছে।

আমি এই সমাধান সব সম্ভব (নেতিবাচক) প্রভাব বিবেচনা না। যদি কোন হয় আমাকে দয়া করে।


মেয়ের চেয়ে একটু বেশি ক্রিয়াপদ, কিন্তু আমি এটা করতে পারি:

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

ব্যক্তিগত পদ্ধতিতে অযৌক্তিক সম্পত্তি রয়েছে যা এটি একটি অ-কনস্টেবল জেড এবং একটি কনস্টেবল ইনস্ট্যান্সের জন্য প্রদান করে, যা এটি ব্যক্তিগত। ব্যক্তিগত পদ্ধতিগুলি বহিরাগত ইন্টারফেসের রূপান্তরগুলি ভেঙ্গে দিতে পারে (এই ক্ষেত্রে ইচ্ছাকৃত ইনভারিয়েন্টটি হল "একটি কনস্টেবল বস্তু এটিতে থাকা বস্তুর মাধ্যমে প্রাপ্ত রেফারেন্সের মাধ্যমে সংশোধন করা যাবে না-একটি")।

মন্তব্যগুলি প্যাটার্নের অংশ - _getZ এর ইন্টারফেসটি নির্দিষ্ট করে যে এটি কল করার জন্য বৈধ নয় (অ্যাক্সেসারদের থেকে বাদে, অবশ্যই): এমনটি করার জন্য কোনও অনুমানযোগ্য সুবিধা নেই কারণ এটি টাইপ করার জন্য আরও 1 টি অক্ষর এবং না ছোট বা দ্রুত কোড ফলে। পদ্ধতি কলিংটি const_cast সহ অ্যাক্সেসারগুলির একটিকে কল করার সমতুল্য, এবং আপনি এটি করতে চাইবেন না। ত্রুটিগুলি সুস্পষ্ট করার বিষয়ে আপনি যদি চিন্তিত হন (এবং এটি একটি ন্যায্য লক্ষ্য) তবে _getZ এর পরিবর্তে এটি const_cast_getZ কল করুন।

যাইহোক, আমি মেয়ের সমাধানটির প্রশংসা করি। আমি এটা কোন দার্শনিক আপত্তি আছে। ব্যক্তিগতভাবে, যদিও, আমি নিয়ন্ত্রিত পুনরাবৃত্তি একটি ক্ষুদ্র বিট পছন্দ করি, এবং একটি ব্যক্তিগত পদ্ধতি যা কিছু দৃঢ়-নিয়ন্ত্রিত পরিস্থিতিতে কেবল লাইনের শব্দ মত দেখায় এমন একটি পদ্ধতিতে বলা উচিত। আপনার বিষ চয়ন করুন এবং এটি সঙ্গে লাঠি।

[সম্পাদনা: কেভিন সঠিকভাবে ইঙ্গিত করেছেন যে _getZ আরও পদ্ধতিতে কল করতে চান (জেনেটেড জেনারেটর) যা জিএসজে একই ভাবে সংশ্লেষিত। এই ক্ষেত্রে, _getZ একটি কনস্টেবল জেড দেখতে পাবে এবং ফিরে যাওয়ার আগে এটি const_cast করতে হবে। এটি এখনও নিরাপদ, কারণ বয়লারপ্লেট অ্যাক্সেসার সবকিছুই নীতিমালা করে তবে এটি নিরাপদ নয় যে এটি অসাধারণভাবে সুস্পষ্ট নয়। উপরন্তু, যদি আপনি এটি করেন এবং তারপরে জেনেটেডকে সর্বদা কনস্টেস্টে পরিবর্তন করতে পরিবর্তন করেন তবে আপনাকে GZ কে সবসময় কনস্টেস্টে পরিবর্তন করতে হবে, তবে কম্পাইলার আপনাকে যা বলবে তা আপনাকে বলবে না।

কম্পাইলার সম্পর্কে পরবর্তী বিন্দু মায়ারের প্রস্তাবিত প্যাটার্নের ক্ষেত্রেও সত্য, তবে একটি অ-সুস্পষ্ট const_cast সম্পর্কে প্রথম পয়েন্টটি নয়। তাই ভারসাম্য নিয়ে আমি মনে করি যে _getZ যদি তার প্রত্যাবর্তনের মানের জন্য const_cast প্রয়োজন হয় তবে এই প্যাটার্নটি মেয়ের্সের উপর তার অনেক মূল্য হারিয়ে ফেলে। যেহেতু এটি মায়ার্সের তুলনায় অসুবিধাগুলিও ভুগছে, তাই আমি মনে করি আমি তার অবস্থানে স্যুইচ করব। একে অপরের থেকে পুনরায় প্রতিক্রিয়া করা সহজ - এটি ক্লাসের অন্য কোনও বৈধ কোডকে প্রভাবিত করে না, কারণ শুধুমাত্র অবৈধ কোড এবং বয়লারপ্লেটটি _getZ কল করে।]


যারা (আমার মত) যারা জন্য

  • সি ++ ব্যবহার করুন 17
  • Boilerplate / পুনরাবৃত্তি এবং অন্তত পরিমাণ যোগ করতে চান
  • ম্যাক্রো ব্যবহার করে মন খারাপ করবেন না (মেটা ক্লাসের জন্য অপেক্ষা করার সময় ...)

এখানে আরেকটি গ্রহণ করা হয়:

#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(a...))>::type {  \
        return const_cast<decltype(func(a...))>(                            \
            std::as_const(*this).func(std::forward<T>(a)...));              \
    }

এটি মূলত @ পাইট, @ ডেভিডস্টোন এবং @ শ 1 এর উত্তরগুলির মিশ্রণ। টেবিলের সাথে এটি কী যোগ করে যে আপনি কোডটির কেবলমাত্র একটি অতিরিক্ত লাইনের সাথে দূরে যা সহজভাবে ফাংশনটি নাম করে (কিন্তু কোন যুক্তি বা ফেরত টাইপ অনুলিপি):

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

দ্রষ্টব্য: GCC 8.1 এর আগে কম্পাইল করতে ব্যর্থ হয়েছে, ক্ল্যাং -5 এবং ঊর্ধ্বমুখী পাশাপাশি এমএসভিসি -19 খুশি ( কম্পাইলার এক্সপ্লোরার অনুসারে)।


সাধারণত, সদস্যের জন্য ফাংশন যা আপনাকে কনস্ট এবং অ-কনস্টেবল সংস্করণগুলি পেতে হয় সেগুলি এবং সেট্টার। বেশিরভাগ সময় তারা এক-লিনিয়ার তাই কোড অনুলিপি একটি সমস্যা নয়।


সি ++ 17 এই প্রশ্নের জন্য সেরা উত্তর আপডেট করেছে:

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

এই সুবিধা আছে যে এটি:

  • কি ঘটছে তা স্পষ্ট
  • সংক্ষিপ্ত কোড ওভারহেড আছে - এটি একটি একক রেখাতে ফিট করে
  • ভুল পেতে কঠিন (শুধুমাত্র দুর্ঘটনা দ্বারা volatile নিক্ষেপ করা যেতে পারে, কিন্তু volatile একটি বিরল যোগ্যতাসম্পন্ন)

যদি আপনি সম্পূর্ণ deduction রুট যেতে চান তাহলে এটি একটি সহায়ক ফাংশন দ্বারা সম্পন্ন করা যাবে

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

এখন আপনি এমনকি আপাতদৃষ্টিতে জগাখিচুড়ি আপ করতে পারেন না, এবং ব্যবহার মত দেখাচ্ছে

T & f() {
    return as_mutable(std::as_const(*this).f());
}

এই DDJ নিবন্ধটি টেমপ্লেট বিশেষজ্ঞ ব্যবহার করে একটি উপায় প্রদর্শন করে যা আপনাকে const_cast ব্যবহার করার প্রয়োজন হয় না। যেমন একটি সহজ ফাংশন জন্য এটা সত্যিই প্রয়োজন হয় না যদিও।

boost :: any_cast (এক পর্যায়ে, এটি আর কিছু নয়) অনুলিপি এড়াতে কনস্টেবল সংস্করণ থেকে একটি const_cast ব্যবহার করে যা নন-কনস্টেক্স সংস্করণটি কল করে। আপনি অ-কনস্টেক্স সংস্করণে কনস্টেমেটিক্স প্রয়োগ করতে পারবেন না তবে আপনাকে তার সাথে খুব সতর্ক থাকতে হবে।

শেষ পর্যন্ত দুইটি স্নিপেট একে অপরের উপরে সরাসরি হিসাবে কিছু কোড অনুলিপি ঠিক আছে।





c++-faq