C++ 如何编写C++;接球手和接球手
如果我需要为属性编写setter和/或getter,我可以这样编写:C++ 如何编写C++;接球手和接球手,c++,getter-setter,C++,Getter Setter,如果我需要为属性编写setter和/或getter,我可以这样编写: struct X { /*...*/}; class Foo { private: X x_; public: void set_x(X value) { x_ = value; } X get_x() { return x_; } }; template <class T> class checked { T v
struct X { /*...*/};
class Foo
{
private:
X x_;
public:
void set_x(X value)
{
x_ = value;
}
X get_x()
{
return 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;
}
};
<>但是我听说这是java写的设置和吸收器的样式,我应该用C++风格来写。此外,有人告诉我这是无效的,甚至是不正确的。这是什么意思?我如何编写C++中的设置器和吸气剂?< /P>
假设对getter和/或setter的需求是合理的。例如,可能我们在setter中执行一些检查,或者可能我们只编写getter。这就是我编写通用setter/getter的方式:
class Foo
{
private:
X x_;
public:
X& x() { return x_; }
const X& x() const { return x_; }
};
我将尝试解释每个转换背后的原因:
您的版本的第一个问题是,您应该传递常量引用,而不是传递值。这避免了不必要的复制。是的,因为C++11
可以移动该值,但这并不总是可能的。对于基本数据类型(例如,int
),使用值而不是引用是可以的
所以我们先纠正一下
class Foo1
{
private:
X x_;
public:
void set_x(const X& value)
// ^~~~~ ^
{
x_ = value;
}
const X& get_x()
// ^~~~~ ^
{
return x_;
}
};
仍然上述解决方案存在问题。由于get_x
不修改对象,因此应将其标记为const
。这是C++原理的一部分,称为const正确性。
上述解决方案不允许您从const
对象获取属性:
const Foo1 f;
X x = f.get_x(); // Compiler error, but it should be possible
这是因为不能对const对象调用不是const方法的get_x
。这样做的理由是非常量方法可以修改对象,因此在常量对象上调用它是非法的
因此,我们进行了必要的调整:
class Foo2
{
private:
X x_;
public:
void set_x(const X& value)
{
x_ = value;
}
const X& get_x() const
// ^~~~~
{
return x_;
}
};
上述变体是正确的。但是在C++中,还有一种写C++的方法,它是C++ + ISH,更少的java ISH。
有两件事需要考虑:
- 我们可以返回对数据成员的引用,如果我们修改了该引用,我们实际上会修改数据成员本身。我们可以用它来编写setter <> LI>在C++方法中可以单独地重载。
int foo()
I编写auto foo()->int
)
class Foo
{
private:
X x_;
public:
auto x() -> X& { return x_; }
auto x() const -> const X& { return x_; }
};
现在我们将调用语法更改为:
Foo2 f;
X x1;
f.set_x(x1);
X x2 = f.get_x();
致:
超越最终版本
出于性能原因,我们可以更进一步,在&&
上重载,并返回对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_); }
};
非常感谢在评论中收到的反馈,特别是感谢StorryTeller对改进此帖子提出的宝贵建议。您的主要错误是,如果在API参数和返回值中不使用引用,那么您可能会在get/set操作(“may”)中执行不必要的拷贝因为如果您使用优化器,您的编译器可能会避免这些副本)
我会这样写:
class Foo
{
private:
X x_;
public:
void x(const X &value) { x_ = value; }
const X &x() const { return x_; }
};
这将保持<强> const正确< <强> >,这是C++的一个非常重要的特性,它与旧的C++版本兼容(另一个答案需要C++ 11)。
您可以将该类用于:
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_
我发现在u或camel case(即getX(),setX())中使用get/set都是多余的,如果您做错了什么,编译器将帮助您解决问题
如果要修改内部Foo::X对象,还可以添加第三个重载X():
…通过这种方式,您可以编写如下内容:
Foo f;
X obj;
f.x() = obj; // replace inner object
f.x().int_member = 1; // replace a single value inside f::x_
但我建议您避免这种情况,除非您确实需要经常修改内部结构(X)。标准库中出现了两种不同形式的“属性”,我将其归类为“面向身份”和“面向价值”。选择哪一个取决于系统应如何与Foo
交互。两者都不是“更正确”
身份导向
class Foo
{
X x_;
public:
X & x() { return x_; }
const X & x() const { return x_; }
}
这里我们返回一个对底层X
成员的引用,它允许呼叫站点的双方观察对方发起的更改。外部世界可以看到X
成员,可能是因为它的身份很重要。乍一看,它可能看起来只有“get”属性的一侧,但如果X
是可赋值的,则情况并非如此
Foo f;
f.x() = X { ... };
价值导向
class Foo
{
X x_;
public:
X x() const { return x_; }
void x(X x) { x_ = std::move(x); }
}
在这里,我们返回X
成员的一个副本,并接受一个副本来覆盖。以后任何一方的更改都不会传播。在这种情况下,我们大概只关心X
的值。多年来,我逐渐相信getter/setter的整个概念通常是一个错误。听起来可能相反,public变量通常是正确答案
诀窍在于公共变量应该是正确的类型。在问题中,您已经指定要么我们编写了一个setter来检查正在编写的值,要么我们只编写了一个getter(因此我们有一个有效的const
对象)
我想说这两个词基本上都是这样说的:“X是一个int,只是它不是一个int-,它确实有点像int,但是有这些额外的限制……”
这就引出了真正的问题:如果仔细观察X表明它确实是一种不同的类型,那么定义它真正的类型,然后将其创建为该类型的公共成员。它的基本结构可能如下所示:
struct X { /*...*/};
class Foo
{
private:
X x_;
public:
void set_x(X value)
{
x_ = value;
}
X get_x()
{
return 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;
}
};
这样,我们就可以安全地将成员公开,因为我们定义的类型实际上就是我们想要的类型——我们想要在其上设置的条件是该类型固有的,而不是由getter/setter在事实之后附加的东西
当然,在这种情况下,我们需要以某种方式限制值。如果我们只需要一个有效的只读类型,那就容易多了——只需要一个定义构造函数和操作符T的模板<
class Foo
{
X x_;
public:
X x() const { return x_; }
void x(X x) { x_ = std::move(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;
}
};
checked<int> foo([](auto i) { return std::min(std::max(i, 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;