检查 C++ 引用 setter 值

Check C++ reference setter value

提问人:eje211 提问时间:10/31/2023 最后编辑:eje211 更新时间:10/31/2023 访问量:81

问:

我有一个关于 C++ 中备受关注的 C++ 引用 getter-setter 的问题。这是基本代码:

class Foo
{
     X x_;
public:
          X & x()       { return x_; }
    const X & x() const { return x_; }
}

我的问题是:如果我在设置值时需要检查该值怎么办?让我们想象一下,我正在设置坐标的纬度。如果我正在编写一个方法,它将如下所示:setLat

void MyClass::setLat(double lat)
{
    if (lat < -90 || lat > 90)
    {
        throw std::range_error("The latitude must be between -90 and 90.");
    }
    _lat = lat;
}

我可以用以下方法做类似的事情吗:

double & MyClass::lat()
{
    return _lat;
}

谢谢

当我看到这个问题顶部的代码是另一个 StackOverflow 问题的答案时,我想,“很酷。但后来,我最终意识到这里的人在说什么:它破坏了封装。此成员也可能是公开的。

由于我不经常使用 C++,所以我不太确定,想。

然而,@ted-lyngmo给出的答案确实很有道理。我认为最终只有一个 regluar getter 和一个 setter 可能是最好的。但我认为有可能在最新版本的 C++ 中模仿 C# 和 Python 的属性能力。看起来是这样。现在:这样做是个好主意吗?这个问题似乎仍然悬而未决。

C++ 引用 getter-setter

评论

2赞 463035818_is_not_an_ai 10/31/2023
返回非常量引用不是封装。如果要封装,则不要返回非常量引用
3赞 BoP 10/31/2023
任何调用方都可以保存返回的引用,然后随时修改成员。该成员也可以公开。
1赞 Eljay 10/31/2023
Getter 看起来像这样: .Setter 将如下所示: .所具有的是封装中断引用。X x() const { return x_; }void set_x(X value) { x_ = std::move(value); }Foo

答:

2赞 463035818_is_not_an_ai 10/31/2023 #1

返回非常量引用会中断封装。一旦调用者获得引用,他们就可以做任何他们喜欢的事情。您想要的行为可以通过返回代理来实现:

#include<iostream>

struct foo {
    struct proxy {
        proxy(int& _value) : _value(_value) {}
        proxy& operator=(int value) {
            if (value != 42) throw "bad value";
            _value = value;
            return *this;
        }
        operator int () { return _value; }
        private:
        int& _value;
    };
    proxy get() { return {x}; }
    int get() const { return x; }
    private:
    int x = 0;
};


int main(){
    foo f;
    f.get() = 42;
    std::cout << f.get();
}

现场演示

但是,提供实际的 setter 要简单得多。foo::set(int)

评论

0赞 Red.Wave 10/31/2023
我会将访问器函数限定为 rvalue(),这样,如果用户代码按值存储它们,则会生成诊断。proxy&&
1赞 HolyBlackCat 10/31/2023
我可能更喜欢带有参数的 setter。
4赞 Ted Lyngmo 10/31/2023 #2

一种选择是创建用户定义的纬度类型,并将所有验证放入其中:

class Latitude {
public:
    Latitude() = default;
    
    explicit Latitude(double lat) : m_latitude(lat) {
        if (m_latitude < -90 || m_latitude > 90)
            throw std::range_error("The latitude must be between -90 and 90.");
    }

    explicit operator double() const { return m_latitude; }

private:
    double m_latitude = 0.;
};

这样一来,就可以使包含纬度的类保持整洁:

class MyClass {
public:
    void setLat(Latitude lat) {
        _lat = lat; 
    }

private:
    Latitude _lat;
};

class MyClass {
public:
    Latitude& latitude() {
        return _lat;
    }

private:
    Latitude _lat;
};

评论

0赞 eje211 10/31/2023
这对我来说现在完全是理论上的,但也许可以用它来制作一个模板,并将检查代码作为 lambda 传递,以便有一个完全可重用的类。我并不是说这是一个好主意,但它可能是可能的。我主要是想弄清楚 C++ 是如何工作的。这是一个很好的例子。谢谢!
0赞 Ted Lyngmo 10/31/2023
@eje211很高兴它有帮助!别客气!是的,当您想使用不同的基础类型执行类似操作时,使用类模板是很好的。可以创建一个类模板,而不是普通的 s 或 s,该模板可以是具有特定有效间隔等的类似类型的基类。intdoubleintervalLatitude
0赞 eje211 11/4/2023
我理解模板化。我用过 Java、C# 和 Scala。这个概念与这些语言中的泛型相同。模板原则上是相同的,但是,就像所有 C++ 一样,它们只是感觉非常不同。(而且我知道它们在引擎盖下根本不像在 JVM 上那样工作。泛型在运行时在 JVM 上实现,在编译时在 C# 和 C++ 的模板中实现。例如,根据我的经验,值类别(rvalue,lvalue,prvalue,xvalue,glvalue)至少在这种程度上只存在于C++中。