“前进 - 牢不可破”的访问者类模板[C ++]

时间:2010-01-07 01:31:53

标签: c++ templates accessor getter-setter

除非我完全弄错,否则getter / setter模式是用于两件事的常见模式:

  1. 通过仅提供getVariable方法(或者,更少见,仅可修改,仅提供setVariable方法)来创建私有变量,以便可以使用它,但从不修改。
  2. 为了确保将来遇到一个问题,一个好的解决方案只是在变量进入和/或退出类之前对其进行处理,您可以通过使用来处理变量getter和setter方法的实际实现,而不是简单地返回或设置值。这样,更改不会传播到代码的其余部分。
  3. 问题#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甚至GetterSetter类模板。

    你有什么想法?

8 个答案:

答案 0 :(得分:4)

虽然从实施的角度来看解决方案很简洁,但从架构上看,它只有一半。 Getter / Setter模式的要点是让clas控制它的数据并减少耦合(即知道如何数据存储的其他类)。该解决方案实现了前者,但不完全是后者。

事实上,其他班级现在必须知道两件事 - 变量的名称和吸气剂上的方法(即.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
}

编辑修复了硬编码分配运算符。如果类型本身具有赋值运算符,这应该可以很好地工作。默认情况下,结构具有那些对于简单的结构,它应该开箱即用。

对于更复杂的类,您需要实现一个足够公平的赋值运算符。通过RVOCopy On Write优化,这在运行时应该相当有效。

答案 1 :(得分:1)

FWIW这是我对你的问题的看法:

  1. 通常,重点是在设置器中强制执行业务逻辑或其他约束。您还可以通过将实例变量与访问器方法分离来获得计算变量或虚拟变量。
  2. 不是我所知道的。我参与过的项目有一系列C宏来消除这些方法
  3. 是;我觉得这很整洁。我只是担心它不值得麻烦,它只会让其他开发人员感到困惑(他们需要更多的概念来适应他们的头脑)并且不会为手动压制这些方法节省太多。

答案 2 :(得分:1)

自从Igor Zevaka发布了一个版本之后,我将发布一篇我很久以前写过的文章。这略有不同 - 我当时观察到大多数实际使用get / set对(实际上做了什么)是强制保持在预定范围内的变量的值。这有点广泛,例如添加I / O运算符,其中提取器仍然强制执行定义的范围。它还有一些测试/练习代码,以显示它的作用及其如何做的一般概念:

#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

答案 3 :(得分:0)

  1. 您还可以使用getter或setter类型方法来获取或设置可计算值,就像在其他语言中使用属性一样,如C#

  2. 我想不出合理的方法来抽象和设置未知数量的值/属性。

  3. 我对C ++ ox标准不太熟悉,无法发表评论。

答案 4 :(得分:0)

这是我认为#define仍然有用的地方。

模板版本复杂且难以理解 - 定义版本很明显

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

class Window
{
    Getter(int, height);
}

我确信我的语法略有错误 - 但你明白了。

如果有一组众所周知的模板,比如说,那么我会使用它们。但我不会写自己的。

答案 5 :(得分:0)

在这种情况下,这可能是矫枉过正,但你应该查看律师/客户的习惯用法,以明智的友谊使用。在找到这个成语之前,我完全避免了友谊。

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

答案 6 :(得分:0)

现在问题是,如果你还需要一个setter怎么办。

我不了解你,但我倾向于(大致)有两种类型:

  • 逻辑课程
  • 斑点

blob只是业务对象的所有属性的松散集合。例如,Person会有surnamefirstname,多个地址,多个专业......所以Person可能没有逻辑。

对于blob,我倾向于使用规范的私有属性+ 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

通过一些预处理器魔术,它可以很好地工作!

#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&amp; / value choice)
  • 那里有更多功能: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:

};

答案 7 :(得分:0)

你正在解决错误的问题。在精心设计的应用程序中,getter和setter应该罕见,而不是自动化的。一个有意义的类提供某种抽象。它不仅仅是一个成员集合,它模拟的概念不仅仅是其成员变量的总和。揭露个别成员通常甚至没有意义。

一个类应该公开对它建模的概念有意义的操作。大多数成员变量用于维护此抽象,以存储您需要的状态。但通常不应直接访问它。 这就是为什么它首先是该类的私人成员

不要找到更简单的方法来编写car.getFrontLeftWheel(),而要问问自己为什么班级的用户首先需要左前轮。你经常在驾驶时直接操纵那个轮子吗?汽车应该为你照顾所有的车轮纺纱业务,不是吗?