如何编写 C++ getter 和 setter

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/51615363/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 15:46:20  来源:igfitidea点击:

How to write C++ getters and setters

c++getter-setter

提问by bolov

If I need to write a setter and/or getter for a property I write it like this:

如果我需要为一个属性编写一个 setter 和/或 getter,我会这样写:

struct X { /*...*/};

class Foo
{
private:
    X x_;

public:
    void set_x(X value)
    {
        x_ = value;
    }
    X get_x()
    {
        return x_;
    }
};

However I have heard that this is the Java styleof writing setters and getters and that I should write it in C++ style. Moreover I was told it is ineficient and even incorrect. What does that mean? How can I write the setters and getters in C++?

但是我听说这是编写 setter 和 getter的Java 风格,我应该用 C++ 风格编写它。此外,有人告诉我这是低效的,甚至是不正确的。这意味着什么?如何在 C++ 中编写 setter 和 getter?



Assume the need for getters and/or setters is justified. E.g. maybe we do some checks in the setter, or maybe we write only the getter.

假设对 getter 和/或 setter 的需求是合理的。例如,我们可能会在 setter 中进行一些检查,或者我们可能只编写 getter。

回答by Caleth

There are two distinct forms of "properties" that turn up in the standard library, which I will categorise as "Identity oriented" and "Value oriented". Which you choose depends on how the system should interact with Foo. Neither is "more correct".

标准库中出现了两种不同形式的“属性”,我将它们归类为“面向身份”和“面向价值”。您选择哪个取决于系统应如何与Foo. 两者都不是“更正确”。

Identity oriented

身份导向

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

Here we return a referenceto the underlying Xmember, which allows both sides of the call site to observe changes initiated by the other. The Xmember is visible to the outside world, presumably because it's identity is important. It may at first glance look like there is only the "get" side of a property, but this is not the case if Xis assignable.

这里我们返回一个底层X成员的引用,它允许调用站点的双方观察对方发起的更改。该X成员对外界可见,大概是因为它的身份很重要。乍一看,属性似乎只有“get”部分,但如果X是可分配的,则情况并非如此。

 Foo f;
 f.x() = X { ... };

Value oriented

价值导向

class Foo
{
     X x_;
public:
     X x() const { return x_; }
     void x(X x) { x_ = std::move(x); }
}

Here we return a copyof the Xmember, and accept a copyto overwrite with. Later changes on either side do not propagate. Presumably we only care about the valueof xin this case.

在这里,我们返回一个副本的的X成员,并接受复制与改写。任何一方的后期更改都不会传播。想必大家只关心价值x在这种情况下。

回答by Jerry Coffin

Over the years, I've come to believe that the whole notion of getter/setter is essentially always a mistake. As @Alf suggested in the comments, a contrary as it may sound, a public variable is normally the correct answer.

多年来,我开始相信 getter/setter 的整个概念本质上总是一个错误。正如@Alf 在评论中所建议的那样,听起来可能相反,公共变量通常是正确的答案。

The trick is that the public variable should be of the correct type. In the question you've specified that either we've written a setter that does some checking of the value being written, or else that we're only writing a getter (so we have an effectively constobject).

诀窍是公共变量应该是正确的类型。在您指定的问题中,要么我们编写了一个 setter 来对正在写入的值进行一些检查,要么我们只编写一个 getter(因此我们有一个有效的const对象)。

I would say that both of those are basically saying something like: "X is an int. Only it's not really an int--it's really something sort of like an int, but with these extra restrictions..."

我会说这两个基本上都是在说:“X 是一个整数。只是它不是真正的整数——它真的有点像一个整数,但有这些额外的限制......”

And that brings us to the real point: if a careful look at X shows that it's really a different type, then define the type that it really is, and then create it as a public member of that type. The bare bones of it might look something like this:

这给我们带来了真正的问题:如果仔细查看 X 表明它确实是一种不同的类型,那么定义它真正的类型,然后将其创建为该类型的公共成员。它的骨架可能看起来像这样:

template <class T>
class checked {
    T value;
    std::function<T(T const &)> check;

public:
    template <class checker>
    checked(checker check) 
        : check(check)
        , value(check(T())) 
    { }

    checked &operator=(T const &in) { value = check(in); return *this; }

    operator T() const { return value; }

    friend std::ostream &operator<<(std::ostream &os, checked const &c) {
        return os << c.value;
    }

    friend std::istream &operator>>(std::istream &is, checked &c) {
        try {
            T input;
            is >> input;
            c = input;
        }
        catch (...) {
            is.setstate(std::ios::failbit);
        }
        return is;
    }
};

This is generic, so the user can specify something function-like (e.g., a lambda) that assures the value is correct--it might pass the value through unchanged, or it might modify it (e.g., for a saturating type) or it might throw an exception--but if it doesn't throw, what it returns must be a value acceptable for the type being specified.

这是通用的,所以用户可以指定一些类似函数的东西(例如,一个 lambda)来确保值是正确的——它可以传递值不变,或者它可以修改它(例如,对于饱和类型)或它可能会抛出异常——但如果它不抛出,它返回的值必须是指定类型可接受的值。

So, for example, to get an integer type that only allows values from 0 to 10, and saturates at 0 and 10 (i.e., any negative number becomes 0, and any number greater than 10 becomes 10, we might write code on this general order:

所以,例如,要得到一个整数类型,它只允许从 0 到 10 的值,并在 0 和 10 处饱和(即,任何负数变为 0,任何大于 10 的数变为 10,我们可能会在这个通用上编写代码命令:

checked<int> foo([](auto i) { return std::min(std::max(i, 0), 10); });

Then we can do more or less the usual things with a foo, with the assurance that it will always be in the range 0..10:

然后我们可以用 a 或多或少地做通常的事情foo,并保证它总是在 0..10 范围内:

std::cout << "Please enter a number from 0 to 10: ";
std::cin >> foo; // inputs will be clamped to range

std::cout << "You might have entered: " << foo << "\n";

foo = foo - 20; // result will be clamped to range
std::cout << "After subtracting 20: " << foo;

With this, we can safely make the member public, because the type we've defined it to be is really the type we want it to be--the conditions we want to place on it are inherent in the type, not something tacked on after the fact (so to speak) by the getter/setter.

有了这个,我们可以安全地将成员公开,因为我们定义它的类型确实是我们想要的类型——我们想要放置在它上面的条件是类型固有的,而不是附加的东西事后(可以这么说)由吸气剂/二传手。

Of course, that's for the case where we want to restrict the values in some way. If we just want a type that's effectively read-only, that's much easier--just a template that defines a constructor and an operator T, but not an assignment operator that takes a T as its parameter.

当然,这是针对我们想以某种方式限制值的情况。如果我们只想要一个有效只读的类型,那就容易多了——只是一个定义构造函数和一个的模板operator T,而不是一个以 T 作为参数的赋值运算符。

Of course, some cases of restricted input can be more complex. In some cases, you want something like a relationship between two things, so (for example) foomust be in the range 0..1000, and barmust be between 2x and 3x foo. There are two ways to handle things like that. One is to use the same template as above, but with the underlying type being a std::tuple<int, int>, and go from there. If your relationships are really complex, you may end up wanting to define a separate class entirely to define the objects in that complex relationship.

当然,某些限制输入的情况可能更复杂。在某些情况下,您需要两件事之间的关系,因此(例如)foo必须在 0..1000 范围内,并且bar必须在 2x 和 3x 之间foo。有两种方法可以处理这样的事情。一种是使用与上面相同的模板,但基础类型是 a std::tuple<int, int>,然后从那里开始。如果您的关系真的很复杂,您可能最终想要完全定义一个单独的类来定义该复杂关系中的对象。

Summary

概括

Define your member to be of the type you really want, and all the useful things the getter/setter could/would do get subsumed into the properties of that type.

将您的成员定义为您真正想要的类型,并且 getter/setter 可以/将要做的所有有用的东西都包含在该类型的属性中。

回答by bolov

This is how I would write a generic setter/getter:

这就是我编写通用 setter/getter 的方式:

class Foo
{
private:
    X x_;

public:
    auto x()       -> X&       { return x_; }
    auto x() const -> const X& { return x_; }
};

I will try to explain the reasoning behind each transformation:

我将尝试解释每个转换背后的推理:

The first issue with your version is that instead of passing around values you should pass const references. This avoids the needless copying. True, since C++11the value can be moved, but that is not always possible. For basic data types (e.g. int) using values instead of references is OK.

您的版本的第一个问题是,您应该传递常量引用而不是传递值。这避免了不必要的复制。没错,因为C++11该值可以移动,但这并不总是可行的。对于基本数据类型(例如int),使用值而不是引用是可以的。

So we first correct for that.

所以我们首先对此进行纠正。

class Foo1
{
private:
    X x_;

public:
    void set_x(const X& value)
//             ^~~~~  ^
    {
        x_ = value;
    }

    const X& get_x()
//  ^~~~~  ^
    {
        return x_;
    }
};

Still there is a problem with the above solution. Since get_xdoes not modify the object it should be marked const. This is part of a C++ principle called const correctness.

上述解决方案仍然存在问题。由于get_x不修改对象,因此应将其标记为const。这是称为const 正确性的 C++ 原则的一部分。

The above solution will not let you get the property from a constobject:

上述解决方案不会让您从const对象中获取属性:

const Foo1 f;

X x = f.get_x(); // Compiler error, but it should be possible

This is because get_xnot being a const method cannot be called on a const object. The rational for this is that a non-const method can modify the object, thus it is illegal to call it on a const object.

这是因为get_x不能在 const 对象上调用不是 const 方法。这样做的原因是非常量方法可以修改对象,因此在 const 对象上调用它是非法的。

So we make the necessary adjustments:

因此,我们进行必要的调整:

class Foo2
{
private:
    X x_;

public:
    void set_x(const X& value)
    {
        x_ = value;
    }

    const X& get_x() const
//                   ^~~~~
    {
        return x_;
    }
};

The above variant is correct. However in C++ there is another way of writting it that is more C++ ish and less Java ish.

上述变体是正确的。然而,在 C++ 中,还有另一种编写它的方式,它更多是 C++ ish,而更少 Java ish。

There are two things to consider:

有两件事需要考虑:

  • we can return a reference to the data member and if we modify that reference we actually modify the data member itself. We can use this to write our setter.
  • in C++ methods can be overloaded by consteness alone.
  • 我们可以返回对数据成员的引用,如果我们修改该引用,我们实际上是在修改数据成员本身。我们可以使用它来编写我们的 setter。
  • 在 C++ 中的方法可以单独通过常量重载。

So with the above knowledge we can write our final elegant C++ version:

因此,有了以上知识,我们就可以编写我们最终的优雅 C++ 版本:

Final version

最终版本

class Foo
{
private:
    X x_;

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

As a personal preference I use the new trailing return function style. (e.g. instead of int foo()I write auto foo() -> int.

作为个人偏好,我使用新的尾随返回函数样式。(例如,而不是int foo()我写auto foo() -> int.

class Foo
{
private:
    X x_;

public:
    auto x()       -> X&       { return x_; }
    auto x() const -> const X& { return x_; }
};

And now we change the calling syntax from:

现在我们将调用语法从:

Foo2 f;
X x1;

f.set_x(x1);
X x2 = f.get_x();

to:

到:

Foo f;
X x1;

f.x() = x1;
X x2 = f.x();
const Foo cf;
X x1;

//cf.x() = x1; // error as expected. We cannot modify a const object
X x2 = cf.x();

Beyond the final version

超越最终版本

For performance reasons we can go a step further and overload on &&and return an rvalue reference to x_, thus allowing moving from it if needed.

出于性能原因,我们可以更进一步,重载&&并返回对 的右值引用x_,从而在需要时允许从它移动。

class Foo
{
private:
    X x_;

public:
    auto x() const& -> const X& { return x_; }
    auto x() &      -> X&       { return x_; }
    auto x() &&     -> X&&      { return std::move(x_); }

};


Many thanks for the feedback received in comments and particularly to StorryTeller for his great suggestions on improving this post.

非常感谢在评论中收到的反馈,特别感谢 StorryTeller 对改进这篇文章提出的很好的建议。

回答by gabry

Your main error is that if you do not use references in the API parameters and return value, so you mayrisk to perform unneeded copies in both get/set operations ("MAY" because if you use the optimizer your compile will probably be able to avoid these copies).

您的主要错误是,如果您不在 API 参数和返回值中使用引用,那么您可能会冒着在 get/set 操作中执行不需要的副本的风险(“MAY”,因为如果您使用优化器,您的编译可能能够避免这些副本)。

I will write it as:

我会把它写成:

class Foo
{
private:
    X x_;
public:
    void x(const X &value) { x_ = value; }
    const X &x() const { return x_; }
};

This will keep the const correctness, that is a very important feature of C++, and it's compatibile with older C++ versions (the other answer requires c++11).

这将保持const 正确性,这是 C++ 的一个非常重要的特性,并且它与旧的 C++ 版本兼容(另一个答案需要 c++11)。

You can use this class with:

您可以将此类用于:

Foo f;
X obj;
f.x(obj);
X objcopy = f.x(); // get a copy of f::x_
const X &objref = f.x(); // get a reference to f::x_

I find the use of get/set superfluous both with _ or camel case (ie getX(), setX()), if you do something wrong the compiler will help you sort it out.

我发现在 _ 或驼峰大小写(即 getX()、setX())中使用 get/set 都是多余的,如果你做错了什么,编译器会帮你解决。

If you want to modify the inner Foo::X object, you can also add a third overload of x():

如果要修改内部的 Foo::X 对象,还可以添加 x() 的第三个重载:

X &x() { return x_; }

.. in this way you can write something like:

..通过这种方式,您可以编写如下内容:

Foo f;
X obj;
f.x() = obj; // replace inner object
f.x().int_member = 1; // replace a single value inside f::x_

but I suggest you to avoid this except if you really need to modify very often the inner struct (X).

但我建议你避免这种情况,除非你真的需要经常修改内部结构(X)。

回答by Hot Since 7cc

Use some IDE for generating. CLion offers the option for inserting getters and setters based on a class member. From there you can see the generated result and follow the same practice.

使用一些IDE进行生成。CLion 提供了基于类成员插入 getter 和 setter 的选项。从那里您可以看到生成的结果并遵循相同的做法。