“前进-坚不可摧”访问器类模板 [C++]

发布于 2024-08-16 21:41:02 字数 1783 浏览 4 评论 0原文

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

  1. 通过仅提供 getVariable 方法来创建私有变量,以便可以使用它,但永远不会修改它(或者,更罕见的是,只能通过仅提供 setVariable 方法进行修改)。
  2. 为了确保将来,如果您碰巧遇到一个问题,一个好的解决方案就是在变量进入和/或离开类之前简单地对其进行处理,那么您可以使用实际的实现来处理该变量在 getter 和 setter 方法上,而不是简单地返回或设置值。这样,更改就不会传播到代码的其余部分。

问题#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 类模板。

你有什么想法?

Unless I am thoroughly mistaken, the getter/setter pattern is a common pattern used for two things:

  1. To make a private variable so that it can be used, but never modified, by only providing a getVariable method (or, more rarely, only modifiable, by only providing a setVariable method).
  2. To make sure that, in the future, if you happen to have a problem to which a good solution would be simply to treat the variable before it goes in and/or out of the class, you can treat the variable by using an actual implementation on the getter and setter methods instead of simply returning or setting the values. That way, the change doesn't propagate to the rest of the code.

Question #1: Am I missing any use of accessors or are any of my assumptions incorrect? I'm not sure if I am correct on those.

Question #2: Are there any sort of template goodness that can keep me from having to write the accessors for my member variables? I didn't find any.

Question #3: Would the following class template be a good way of implementing a getter without having to actually write the accesor?

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

It looks fair to me, and I could even reimplement the get logic by using some other partial template specialization trickery. The same can be applied to some sort of Setter or even GetterSetter class templates.

What are your thoughts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

故笙诉离歌 2024-08-23 21:41:02

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

事实上,另一个类现在必须知道两件事 - 变量的名称和 getter 上的方法(即 .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 优化,这在运行时应该相当有效。

Whilst the solution is neat from implementation point of view, architectually, it's only halfway there. The point of the Getter/Setter pattern is to give the clas control over it's data and to decrease coupling (i.e. other class knowing how data is stored). This solution achieves the former but not quite the latter.

In fact the other class now has to know two things - the name of the variable and the method on the getter (i.e. .get()) instead of one - e.g. getWidth(). This causes increased coupling.

Having said all that, this is splitting proverbial architectural hairs. It doesn't matter all that much at the end of the day.

EDIT OK, now for shits and giggles, here is a version of the getter using operators, so you don't have to do .value or .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
}

EDIT Fixed hardcoded assignment operator. This should work reasonably well if the type itself has an assignment operator. By default structs have those so for simple ones it should work out of the box.

For more complex classes you will need to implement an assignment operator which is fair enough. With RVO and Copy On Write optimizations, this should be reasonably efficient at run time.

抚笙 2024-08-23 21:41:02

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

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

FWIW here are my opinions on your questions:

  1. Typically the point is that there is business logic or other constraints enforced in the setter. You can also have calculated or virtual variables by decoupling the instance variable with accessor methods.
  2. Not that I know of. Projects I've worked on have had a family of C macros to stamp out such methods
  3. Yes; I think that's pretty neat. I just worry it's not worth the trouble, it'll just confuse other developers (one more concept they need to fit in their head) and isn't saving much over stamping out such methods manually.
半暖夏伤 2024-08-23 21:41:02

由于 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

Since Igor Zevaka posted one version of this, I'll post one I wrote a long time ago. This is slightly different -- I observed at the time that most real use of get/set pairs (that actually did anything) was to enforce the value of a variable staying within a pre-determined range. This is a bit more extensive, such as adding I/O operators, where extractor still enforces the defined range. It also has a bit of test/exercise code to show the general idea of what it does and how it does it:

#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
背叛残局 2024-08-23 21:41:02
  1. 您还可以使用 getter 或 setter 类型方法来获取或设置可计算值,就像 C# 等其他语言中使用属性的方式一样

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

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

  1. You can also use a getter or setter type method to get or set computable values, much the way properties are used in other languages like C#

  2. I can't think of reasonable way to abstract the getting and setting of an unknown number of values / properties.

  3. I'm not familiar enough with the C++ox standard to comment.

残龙傲雪 2024-08-23 21:41:02

在这种情况下,这可能有点过分了,但你应该检查一下律师/客户的惯用语,以明智地使用友谊。在发现这个成语之前,我完全回避了友谊。

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

This may be overkill in this case but you should check out the attorney/client idiom for judicious friendship usage. Before finding this idiom, I avoided friendship altogether.

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

凉城 2024-08-23 21:41:02

现在的问题是,如果您还需要一个 setter 该怎么办?

我不了解您的情况,但我倾向于(大致)有两种类型的类:

  • 逻辑
  • blob

的类blob 只是业务对象的所有属性的松散集合。例如,一个Person将有一个姓氏名字、几个地址、几个职业......所以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& /值选择)
  • 那里有更多的功能: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:

};

And now the question, and what if you need a setter as well.

I don't know about you, but I tend to have (roughly) two types of classes:

  • class for the logic
  • blobs

The blobs are just loose collections of all the properties of a Business Object. For example a Person will have a surname, firstname, several addresses, several professions... so Person may not have logic.

For the blobs, I tend to use the canonical private attribute + getter + setter, since it abstracts the actual implementation from the client.

However, although your template (and its evolution by Igor Zeveka) are really nice, they do not address the setting problem and they do not address binary compatibility issues.

I guess I would probably resort to macros...

Something like:

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

Which would be used like:

// 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

With a bit of preprocessor magic it would work quite well!

#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)

Okay, not type safe, and all, but:

  • it's a reasonable set of macro I think
  • it's easy to use, the user only ever have to worry about 2 macros after all, though like templates the errors could get hairy
  • use of boost.call_traits for efficiency (const& / value choice)
  • there is more functionality there: getter/setter duo

  • it is, unfortunately, a set of macros... and will not complain if you ever

  • it wreaks havoc on the accessors (public, protected, private) so it's best not to intersped it throughout the class

Here is the canonical example then:

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:

};
爱已欠费 2024-08-23 21:41:02

你正在解决错误的问题。在设计良好的应用程序中,getter 和 setter 应该很少见,而不是自动化。有意义的类提供某种抽象。它不仅仅是成员的集合,它建模的概念不仅仅是其成员变量的总和。而且通常情况下,暴露个别成员也是没有意义的。

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

与其寻找更简单的方法来编写 car.getFrontLeftWheel(),不如问问自己为什么该类的用户首先需要左前轮。您开车时通常直接操纵车轮吗?汽车应该为你处理所有的车轮转动事务,不是吗?

You're solving the wrong problem. In a well-designed application, getters and setters should be rare, not automated. A meaningful class provides some kind of abstraction. It is not simply a collection of members, it models a concept that is more than just the sum of its member variables. And it typically doesn't even make sense to expose individual members.

A class should expose the operations that make sense on the concept that it models. Most member variables are there to maintain this abstraction, to store the state that you need. But it typically shouldn't be accessed directly. That is why it is a private member of the class in the first place.

Rather than finding easier ways to write car.getFrontLeftWheel(), ask yourself why the user of the class would ever need the front left wheel in the first place. Do you usually manipulate that wheel directly when driving? The car is supposed to take care of all the wheel-spinning business for you, isn't it?

爱她像谁 2024-08-23 21:41:02

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

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

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

class Window
{
    Getter(int, height);
}

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

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

This is where I think #defines are still useful.

The template version is complicated and hard to understand - the define version is obvious

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

class Window
{
    Getter(int, height);
}

I am sure I have the syntax slightly wrong - but you get the point.

If there was a well known set of templates in, say, boost then I would use them. But I would not write my own.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文