قوالب فئة Accessor Accessor "إلى الأمام غير القابلة للكسر [C ++

StackOverflow https://stackoverflow.com/questions/2017623

سؤال

ما لم أكن مخطئا تماما، فإن نمط Getter / Setter هو نمط شائع يستخدم لأمين شيئين:

  1. لإجراء متغير خاص بحيث يمكن استخدامه، ولكن لم يتم تعديله أبدا، من خلال توفير أ getVariable الطريقة (أو، نادرا ما تكون قابلة للتعديل فقط، من خلال توفير أ setVariable طريقة).
  2. للتأكد من أنه في المستقبل، في المستقبل، إذا كان لديك مشكلة في أن الحل الجيد سيكون ببساطة لمعالجة المتغير قبل أن يذهب و / أو الخروج من الفئة، يمكنك التعامل مع المتغير باستخدام التنفيذ الفعلي في أساليب Getter و STERTER بدلا من إرجاع القيم أو تحديدها ببساطة. بهذه الطريقة، لا ينتشر التغيير إلى بقية الرمز.

سؤال رقم 1: هل أفتقد أي استخدام للمسافرين أم أن أي من افتراضاتي غير صحيحة؟ لست متأكدا مما إذا كنت صحيحا على هؤلاء.

سؤال رقم 2: هل هناك أي نوع من القالب الخير الذي يمكن أن تبقيني من الاضطرار إلى كتابة مواقف المتغيرات الخاصة بي؟ لم أجد أي.

سؤال رقم 3: هل ستكون قالب الفصل الدراسي التالي طريقة جيدة لتنفيذ Getter دون الحاجة إلى كتابة الأسهم بالفعل؟

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend class TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    template <typename ... Args>
    Getter(Args args) : value(args ...) {} // Uses C++0x

    T get() { return value; }

protected:
    T value;
};

class Window
{
public:
    Getter<uint32_t,Window> width;
    Getter<uint32_t,Window> height;

    void resize(uint32_t width,uint32_t height)
    {
        // do actual window resizing logic

        width.value = width; // access permitted: Getter befriends Window
        height.value = height; // same here
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method

    // This works: Getter::get() is public
    std::cout << "Current window size: " << win.width.get() << 'x' << win.height.get() << ".\n";

    // This doesn't work: Getter::value is private
    win.width.value = 640;
    win.height.value = 480;
}

يبدو عادل بالنسبة لي، ويمكنني حتى إعادة تشغيل get المنطق باستخدام بعض الخداع تخصص القالب الجزئي الآخر. يمكن تطبيق الشيء نفسه على نوع من قوالب SETTER أو حتى قوالب فئة GetStersetter.

ما رأيك؟

هل كانت مفيدة؟

المحلول

في حين أن الحل أنيق من وجهة نظر التنفيذ، في البندسة، إنه فقط في منتصف الطريق هناك. نقطة نمط Getter / Setter هو إعطاء التحكم في CLAS على بياناتها وتقليل الاقتران (أي معرفة فئة أخرى كيف يتم تخزين البيانات). يحقق هذا الحل السابق ولكن ليس الأخير.

في الواقع، يجب على الطبقة الأخرى الآن معرفة شيئين - اسم المتغير والطريقة الموجودة على Getter (IE .get()) بدلا من واحد - على سبيل المثال getWidth(). وبعد هذا يسبب زيادة اقتران.

بعد أن قال كل ذلك، هذا هو تقسيم الشعر المعماري المثلي. لا يهم كل ما في نهاية اليوم.

تعديل حسنا، الآن للحصول على القرف والضحكات، إليك نسخة من Getter باستخدام المشغلين، لذلك لا يتعين عليك القيام به .value أو .get()

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    operator T()
    {
        return value;
    }

protected:
    T value;

    T& operator=( T other )
    {
       value = other;
       return value;  
    }


};

class Window
{
public:
    Getter<int,Window> _width;
    Getter<int,Window> _height;

    void resize(int width,int height)
    {
        // do actual window resizing logic
        _width = width; //using the operator
        _height = height; //using the operator
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method
    int w2 = win._width; //using the operator
    //win._height = 480; //KABOOM
}

تعديل مشغل التعيين المضغوط الثابت. هذا يجب أن يعمل بشكل جيد بشكل معقول إذا كان النوع نفسه لديه مشغل التعيين. من خلال الهياكل الافتراضية لديها أولئك حتى بالنسبة لتلك البساطة يجب أن تعمل خارج الصندوق.

للحصول على دروس أكثر تعقيدا، ستحتاج إلى تنفيذ مشغل تعيين عادل بما فيه الكفاية. مع RVO. و نسخ على الكتابة التحسينات، يجب أن يكون هذا فعالا بشكل معقول في وقت التشغيل.

نصائح أخرى

FWIW هنا آرائي على أسئلتك:

  1. عادة ما تكون هذه النقطة مفادها أن هناك منطق عمل أو غيرها من القيود المنفذة في SETTER. يمكنك أيضا تحديد المتغيرات المحسوبة أو الظاهري عن طريق فصل المتغير المثيل مع طرق Accessor.
  2. ليس هذا ما أعلمه عن. كانت المشاريع التي عملت عليها في عائلة من وحدات وحدات الماكرو C لختم هذه الأساليب
  3. نعم؛ أعتقد أن هذا أنيق جدا. أنا فقط أشعر بالقلق من أنها لا تستحق المشكلة، بل سوف تخلط بين المطورين الآخرين (مفهوم آخر يحتاجون إلى تنسيقه في رأسهم) ولا ينقذ الكثير من الأساليب هذه يدويا.

نظرا لأن إيغور زيفاكا نشر نسخة واحدة من هذا، فسوف نشر واحدة كتبت منذ وقت طويل. هذا مختلف قليلا - لاحظت في ذلك الوقت عظم الاستخدام الحقيقي من أزواج الحصول على / تعيين (التي فعلت فعلا أي شيء) هو تطبيق قيمة البقاء المتغير ضمن نطاق محدد مسبقا. هذا أكثر شمولا، مثل إضافة مشغلي الإدخال / الإخراج، حيث ما زال النازع يفرض النطاق المحدد. كما أن لديها القليل من التعليمات البرمجية الاختبار / التمرين لإظهار الفكرة العامة لما تفعله وكيف يفعل ذلك:

#include <exception>
#include <iostream>
#include <functional>

template <class T, class less=std::less<T> >
class bounded {
    const T lower_, upper_;
    T val_;

    bool check(T const &value) {
        return less()(value, lower_) || less()(upper_, value);
    }

    void assign(T const &value) {
        if (check(value))
            throw std::domain_error("Out of Range");
        val_ = value;
    }

public:
    bounded(T const &lower, T const &upper) 
        : lower_(lower), upper_(upper) {}

    bounded(bounded const &init) 
        : lower_(init.lower), upper_(init.upper)
    { 
        assign(init); 
    }

    bounded &operator=(T const &v) { assign(v);  return *this; }

    operator T() const { return val_; }

    friend std::istream &operator>>(std::istream &is, bounded &b) {
        T temp;
        is >> temp;

        if (b.check(temp))
            is.setstate(std::ios::failbit);
        else
            b.val_ = temp;
        return is;
    }
};


#ifdef TEST

#include <iostream>
#include <sstream>

int main() {
    bounded<int> x(0, 512);

    try {
        x = 21;
        std::cout << x << std::endl;

        x = 1024;
        std::cout << x << std::endl;
    }

    catch(std::domain_error &e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    std::stringstream input("1 2048");
    while (input>>x)
        std::cout << x << std::endl; 

    return 0;
}

#endif
  1. يمكنك أيضا استخدام طريقة نوع Getter أو Stredter للحصول على قيم حسابية أو تعيينها، تم استخدام خصائص طريقة أخرى بلغات أخرى مثل C #

  2. لا أستطيع التفكير في طريقة معقولة لتجريد الحصول على وعدد غير معروف من القيم / الخصائص.

  3. أنا لست مألوفا بما فيه الكفاية مع معيار الثور C ++ للتعليق.

قد يكون هذا مبالغة في هذه الحالة، لكن يجب عليك التحقق من المحامي / العميل Idiom لاستخدام الصداقة الحكيم. قبل العثور على هذا المصطلح، تجنبت الصداقة تماما.

http://www.ddj.com/cpp/184402053/

والآن السؤال، وما إذا كنت بحاجة إلى setter أيضًا.

لا أعرف عنك، لكنني أميل إلى الحصول على (تقريبا) نوعين من الفصول:

  • فئة للمنطق
  • البلاط

النقط هي مجرد مجموعات فضفاضة من جميع خصائص كائن أعمال. على سبيل المثال أ Person سوف يكون لها surname, firstname, عدة عناوين، عدة مهن ... Person قد لا يكون منطق.

بالنسبة إلى النقط، أميل إلى استخدام السمة الخاصة Canonical Private + Getter + Setter، لأنه ملخص التنفيذ الفعلي من العميل.

ومع ذلك، على الرغم من أن القالب الخاص بك (وتطوره من قبل Igor Zeveka) لطيفة حقا، فإنها لا تعالج مشكلة الإعداد ولا تعالجها التوافق الثنائي مسائل.

أعتقد أنني ربما ألجع إلى وحدات ماكرو ...

شيء مثل:

// Interface
// Not how DEFINE does not repeat the type ;)
#define DECLARE_VALUE(Object, Type, Name, Seq) **Black Magic Here**
#define DEFINE_VALUE(Object, Name, Seq) ** Black Magic Here**

// Obvious macros
#define DECLARE_VALUER_GETTER(Type, Name, Seq)\
   public: boost::call_traits<Type>::const_reference Name() const

#define DEFINE_VALUE_GETTER(Object, Name)\
   boost::call_traits<Name##_type>::const_reference Object::Name ()const\
   { return m_##Name; }

#define DECLARE_VALUE_SETTER(Object, Type, Name)\
   public: Type& Name();\
   public: Object& Name(boost::call_traits<Type>::param_type i);

#define DEFINE_VALUE_SETTER(Object, Name)\
   Name##_type& Object::Name() { return m_##Name; }\
   Object& Object::Name(boost::call_traits<Name##_type>::param_type i)\
   { m_##Name = i; return *this; }

والتي سوف تستخدم مثل:

// window.h
DECLARE_VALUE(Window, int, width, (GETTER)(SETTER));

// window.cpp
DEFINE_VALUE(Window, width, (GETTER)); // setter needs a bit of logic

Window& Window::width(int i) // Always seems a waste not to return anything!
{ 
  if (i < 0) throw std::logic_error();
  m_width = i;
  return *this;
} // Window::width

مع القليل من السحر preprocessor سوف تعمل بشكل جيد للغاية!

#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/rem.hpp>

#define DECLARE_VALUE_ITER(r, data, elem)\
  DECLARE_VALUE_##elem ( BOOST_PP_TUPLE_REM(3)(data) )

#define DEFINE_VALUE_ITER(r, data, elem)\
  DEFINE_VALUE_##elem ( BOOST_PP_TUPLE_REM(2)(data) )

#define DECLARE_VALUE(Object, Type, Name, Seq)\
   public: typedef Type Name##_type;\
   private: Type m_##Name;\
   BOOST_PP_SEQ_FOREACH(DECLARE_VALUE_ITER, (Object, Type, Name), Seq)

#define DEFINE_VALUE(Object, Name, Seq)\
   BOOST_PP_SEQ_FOREACH(DEFINE_VALUE_ITER, (Object, Name), Seq)

حسنا، عدم الكتابة آمنة، وكل ذلك، ولكن:

  • إنها مجموعة معقولة من ماكرو وأعتقد
  • من السهل الاستخدام، يجب أن تقلق المستخدم فقط حوالي 2 وحدات ماكرو بعد كل شيء، على الرغم من أن القوالب التي يمكن أن تحصل عليها الأخطاء شعر
  • استخدام BOOST.CALL_TRAITS الكفاءة (CONST و / خيارات القيمة)
  • هناك المزيد من الوظائف هناك: Getter / Setter Duo

  • إنه، لسوء الحظ، مجموعة من وحدات الماكرو ... ولن تشكو إذا كنت من أي وقت مضى

  • تقوم بتسهيل الخراب على الملحقات (الجمهور، المحمي، الخاص)، لذلك من الأفضل عدم تقاطعه طوال الفصل

هنا المثال الكنسي ثم:

class Window
{
  // Best get done with it
  DECLARE_VALUE(Window, int, width, (GETTER));
  DECLARE_VALUE(Window, int, height, (GETTER));

// don't know which is the current access level, so better define it
public:

};

أنت حل المشكلة الخاطئة. في تطبيق جيد تصميم جيد، يجب أن يكون Getters و Setters نادر, ، وليس الآلي. فئة ذات مغزى توفر نوعا ما من التجريد. وبعد إنها ليست مجرد مجموعة من الأعضاء، فهي نماذج مفهوم أكثر من مجرد مجموع المتغيرات الأعضاء فيها. وعادة ما لا معنى له في فضح أعضاء الفرد.

يجب أن تعرض الفصل العمليات التي لها معنى على المفهوم أن نماذجها. معظم المتغيرات الأعضاء موجودة للحفاظ على هذه التجريد، لتخزين الحالة التي تحتاج إليها. ولكن عادة لا ينبغي الوصول إليها مباشرة. هذا هو السبب في أنه عضو خاص في الفصل في المقام الأول.

بدلا من العثور على طرق أسهل للكتابة car.getFrontLeftWheel(), ، اسأل نفسك لماذا يحتاج مستخدم الفصل إلى عجلة الأيسر الأمامية في المقام الأول. هل عادة ما تتتلاعب بهذه العجلة مباشرة عند القيادة؟ من المفترض أن تهتم السيارة بجميع أعمال الغزل العجلة بالنسبة لك، أليس كذلك؟

هذا هو المكان الذي أعتقد أنه #defineS لا تزال مفيدة.

إصدار القالب معقد ويصعب فهمه - إصدار تحديد واضح

#define Getter(t, n)\
     t n;\
     t get_##n() { return n; }

class Window
{
    Getter(int, height);
}

أنا متأكد من أنني لدي بناء الجملة خطأ قليلا - لكنك تحصل على هذه النقطة.

إذا كان هناك مجموعة معروفة من القوالب، فقرا، بزيادة، ثم أود استخدامها. لكنني لن أكتب بلدي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top