使用值包装器和 operator() 重载来简化 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是否危险。

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

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

因此,从这一点来看,我认为您的解决方案是节省您多输入几行代码的有效方法。

我在那里没有看到特别危险的东西,但它似乎没有任何收获。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

它本身并不危险,只是更难使用(对于类用户)。 MyClass2 混淆了界面并使其混乱。

它可能会使您编写代码变得不那么乏味,但您只需要编写一次代码 - 该接口将被多次重用。 因此,应该青睐代码阅读器。

因此,MyClass1优于MyClass2和Potatoswatter解决方案。

(即使C++有办法做到这一点:

class MyClass3
{
    double x, y, z;
}
MyClass3::z::operator=(double) { ... } // property set
double MyClass3::z::operator() { ... } // property get

就像在 C# 中一样,我认为这仍然是一个坏主意,因为这种"隐藏"代码会导致错误的假设,从而减慢调试速度。 函数样式至少让你认为可能有一些比原始副本更复杂的事情发生。

最新更新