使用值包装器和operator()重载来简化getter / setter设计:一种危险的做法?

时间:2013-01-25 04:13:34

标签: c++ c++11 wrapper getter-setter

考虑以下课程:

class MyClass1
{
    public:
        double x() const {return _x;} // getter
        double y() const {return _y;} // getter
        double z() const {return _x*_y;} // getter
        void x(const double var) {_x = var;} // setter
        void y(const double var) {_y = var;} // setter
        void z(const double var) {_x = var; _y = 1;} // setter
    protected:
        double _x;
        double _y;
};

由于MyClass1的实际内容是一个实现细节,getter和setter提供了一种统一的方法来获取和设置类内容,即使它们是相互依赖的(此处_z内部不存在但是对于用户,z是一个变量,如xy)。

现在为了避免必须为xy编写getter / setter,可以使用这样的包装器:

template <typename Type>
class Wrapper
{
    public:
        constexpr Wrapper(const Type& value) {_value = value;}
        constexpr Type& operator()() {return _value;}
        constexpr const Type& operator()() const {return _value;}
        constexpr void operator()(const Type& value) {_value = value;}
    protected:
        _value;
};

现在原来的班级成了:

class MyClass2
{
    public:
        Wrapper<double> x;
        Wrapper<double> y;
        double z() const {return x*y;} // getter
        void z(const double var) {x = var; y = 1;} // setter
};

避免必须编写getter / setter是危险的做法还是一个好的解决方案?

注意:此处MyClass1MyClass2只是示例。我的问题非常“一般”:当getter / setter返回/设置内部值时,用提议的Wrapper替换类的getter / setter是危险的。

3 个答案:

答案 0 :(得分:1)

正如您所提到的,getter和setter的主要目的是提供访问和设置私有实例变量的统一方法。

从你的包装器解决方案中,如果在程序生命周期的某个时间你决定更改一个setter,你可以扔掉包装器并用MyClass1中的原始getter / setter替换它 - 而不必改变所有其他调用它的代码的组件。

从那时起,我认为您的解决方案是一种有效的方法,可以避免输入额外的几行代码。

答案 1 :(得分:1)

我认为没有什么特别危险,但它似乎没有任何收获。 C ++为任何类型的对象提供引用语义,这与setter函数不兼容。有时,实际内容不仅仅是一个细节。

你也可以走另一条路(事实上,这是我在点击这个问题时所期望的):

struct virt_z {
    double x;
    double y;
    struct z_wrap {
        virt_z &parent;

        operator double() { return parent.x * parent.y; }
        z_wrap &operator=( double in ) {
            parent.x = in;
            parent.y = 1;
            return *this;
        }
        z_wrap( virt_z &in ) : parent( in ) {}
    } z;

    virt_z() : z( *this ) {}
    virt_z( virt_z const &o ) : x( o.x ), y( o.y ), z( *this ) {}
};

https://ideone.com/qqgj3D

答案 2 :(得分:1)

这并不危险,使用起来比较困难(对于班级用户)。 MyClass2模糊了界面并使其混乱。

这可能会让您编写代码变得不那么繁琐,但您只需编写一次代码 - 该接口将被重复使用多次。因此,代码阅读器应该受到青睐。

出于这个原因,MyClass1优于MyClass2和Potatoswatter解决方案。

(即使在C ++中有一种方法可以做到这一点:

class MyClass3
{
    double x, y, z;
}

MyClass3::z::operator=(double) { ... } // property set
double MyClass3::z::operator() { ... } // property get
像C#一样,我认为这仍然是一个坏主意,因为这种“隐藏”的代码会导致错误的假设,从而减慢调试速度。函数样式至少让你觉得可能会有比原始副本更复杂的东西。)