定义非常量 'get' 成员函数的原因?
Reasons for defining non-const 'get' member functions?
我正在通过 Stroustrup 的(使用 C++ 的编程原理和实践)一书学习 C++。在一个练习中,我们定义了一个简单的结构:
template<typename T>
struct S {
explicit S(T v):val{v} { };
T& get();
const T& get() const;
void set(T v);
void read_val(T& v);
T& operator=(const T& t); // deep copy assignment
private:
T val;
};
然后我们被要求定义一个 const 和一个非常量成员函数来得到 val
.
我想知道:在任何情况下,使用 returns val
的非常量 get
函数是否有意义?
在我看来,在这种情况下我们不能间接更改值,这似乎更清晰。您需要一个 const 和一个非 const get
函数来 return 成员变量的用例可能是什么?
get()
可以被允许变异的非 const 对象调用,你可以这样做:
S r(0);
r.get() = 1;
但是如果你将 r
const 设为 const S r(0)
,行 r.get() = 1
不再编译,甚至无法检索值,这就是为什么你需要一个 const 版本 const T& get() const
至少能够检索 const 对象的值,这样做可以让您:
const S r(0)
int val = r.get()
成员函数的 const 版本尽量与调用对象的 constness 属性 保持一致,即如果对象是不可变的const 和成员函数 returns 引用,它可以通过返回一个 const 引用来反映调用者的 constness,从而保持对象的不变性 属性。
非常量 getters?
getter 和 setters 只是约定俗成的。有时使用的成语不是提供 getter 和 setter,而是提供类似于
的内容
struct foo {
int val() const { return val_; }
int& val() { return val_; }
private:
int val_;
};
这样,根据实例的常量性,您可以获得引用或副本:
void bar(const foo& a, foo& b) {
auto x = a.val(); // calls the const method returning an int
b.val() = x; // calls the non-const method returning an int&
};
总的来说这是否是好的风格是见仁见智的。在某些情况下它会引起混淆,而在其他情况下这种行为正是您所期望的(见下文)。
无论如何,根据class应该做什么以及你想如何使用它来设计class的界面比盲目遵循约定更重要setters 和 getters(例如,你应该给方法一个有意义的名称来表达它的作用,而不仅仅是 "pretend to be encapsulated and now provide me access to all your internals via getters",这就是使用 getters无处不在实际上意味着)。
具体例子
想想容器中的元素访问通常是这样实现的。举个例子:
struct my_array {
int operator[](unsigned i) const { return data[i]; }
int& operator[](unsigned i) { return data[i]; }
private:
int data[10];
};
向用户隐藏元素不是容器的工作(甚至 data
也可能是 public)。您不希望根据您是要读取还是写入元素而使用不同的方法来访问元素,因此在这种情况下提供 const
和非常量重载非常有意义。
来自 get 与封装的非常量引用
可能不是那么明显,但是提供getters和setters是支持封装还是相反有点争议。虽然一般来说这个问题在很大程度上是基于意见的,但对于 return 非常量引用的 getter 来说,它与意见无关。他们确实打破了封装。考虑
struct broken {
void set(int x) {
counter++;
val = x;
}
int& get() { return x; }
int get() const { return x; }
private:
int counter = 0;
int value = 0;
};
这个class顾名思义就是坏了。客户端可以简单地获取一个引用,class 没有机会计算值被修改的次数(正如 set
所建议的)。一旦你 return 是一个非 const 引用,那么关于封装与使成员 public 几乎没有区别。因此,这仅用于这种行为是自然的情况(例如容器)。
PS
请注意,您的示例 return 是 const T&
而不是值。这对于模板代码来说是合理的,因为您不知道副本有多昂贵,而对于 int
,您不会通过 returning const int&
而不是 int
获得太多收益.为了清楚起见,我使用了非模板示例,但对于模板化代码,您可能更愿意 return a const T&
.
这取决于 S
的目的。如果它是某种薄包装器,允许用户直接访问底层值可能是合适的。
现实生活中的一个例子是 std::reference_wrapper
。
没有。如果 getter 只是 return 对成员的非常量引用,如下所示:
private:
Object m_member;
public:
Object &getMember() {
return m_member;
}
那么 m_member
应该是 public 而不是,不需要访问器。绝对没有必要将此成员设为私有,然后创建一个访问器,它为 all 提供访问权限。
如果你调用 getMember()
,你可以将结果引用存储到 pointer/reference,然后你可以用 m_member
做任何你想做的事,封闭的 class 会一无所知。是一样的,就好像 m_member
变成了 public.
注意,如果 getMember()
做一些额外的任务(例如,它不只是简单地 return m_member
,而是懒惰地构造它),那么 getMember()
可能有用:
Object &getMember() {
if (!m_member) m_member = new Object;
return *m_member;
}
首先让我重新表述一下你的问题:
Why have a non-const getter for a member, rather than just making the member public?
几个可能的原因原因:
1。易于检测
谁说非常量 getter 需要只是:
T& get() { return val; }
?它很可能是这样的:
T& get() {
if (check_for_something_bad()) {
throw std::runtime_error{
"Attempt to mutate val when bad things have happened");
}
return val;
}
但是,正如@BenVoigt 所建议的,更合适的做法是等到调用者实际尝试通过引用改变值,然后再发出错误。
2。文化公约/《老板这么说》
一些组织强制执行编码标准。这些编码标准有时是由可能过度防御的人编写的。因此,您可能会看到如下内容:
Unless your class is a "plain old data" type, no data members may be public. You may use getter methods for such non-public members as necessary.
然后,即使特定 class 只允许非常量访问是有意义的,它也不会发生。
3。也许 val
不存在?
您给出了一个示例,其中 val
实际上 存在于 class 的实例中。但实际上 - 它不必! get()
方法可以 return 某种 proxy object,它在分配、变异等时执行一些计算(例如,在数据库中存储或检索数据;或翻转一点,这本身不像对象需要的那样是可寻址的。
4。允许稍后更改 class 内部结构而不更改用户代码
现在,阅读上面的第 1 项或第 3 项,您可能会问“但是我的 struct S
有 val
!”或“我的 get()
没有做任何有趣的事情!” - 嗯,真的,他们没有;但您将来可能想更改此行为。如果没有 get()
,您 class' 的所有用户都需要更改他们的代码。使用 get()
,您只需更改 struct S
.
的实现
现在,我不提倡这种设计方法,但有些程序员提倡。
我正在通过 Stroustrup 的(使用 C++ 的编程原理和实践)一书学习 C++。在一个练习中,我们定义了一个简单的结构:
template<typename T>
struct S {
explicit S(T v):val{v} { };
T& get();
const T& get() const;
void set(T v);
void read_val(T& v);
T& operator=(const T& t); // deep copy assignment
private:
T val;
};
然后我们被要求定义一个 const 和一个非常量成员函数来得到 val
.
我想知道:在任何情况下,使用 returns val
的非常量 get
函数是否有意义?
在我看来,在这种情况下我们不能间接更改值,这似乎更清晰。您需要一个 const 和一个非 const get
函数来 return 成员变量的用例可能是什么?
get()
可以被允许变异的非 const 对象调用,你可以这样做:
S r(0);
r.get() = 1;
但是如果你将 r
const 设为 const S r(0)
,行 r.get() = 1
不再编译,甚至无法检索值,这就是为什么你需要一个 const 版本 const T& get() const
至少能够检索 const 对象的值,这样做可以让您:
const S r(0)
int val = r.get()
成员函数的 const 版本尽量与调用对象的 constness 属性 保持一致,即如果对象是不可变的const 和成员函数 returns 引用,它可以通过返回一个 const 引用来反映调用者的 constness,从而保持对象的不变性 属性。
非常量 getters?
getter 和 setters 只是约定俗成的。有时使用的成语不是提供 getter 和 setter,而是提供类似于
的内容struct foo {
int val() const { return val_; }
int& val() { return val_; }
private:
int val_;
};
这样,根据实例的常量性,您可以获得引用或副本:
void bar(const foo& a, foo& b) {
auto x = a.val(); // calls the const method returning an int
b.val() = x; // calls the non-const method returning an int&
};
总的来说这是否是好的风格是见仁见智的。在某些情况下它会引起混淆,而在其他情况下这种行为正是您所期望的(见下文)。
无论如何,根据class应该做什么以及你想如何使用它来设计class的界面比盲目遵循约定更重要setters 和 getters(例如,你应该给方法一个有意义的名称来表达它的作用,而不仅仅是 "pretend to be encapsulated and now provide me access to all your internals via getters",这就是使用 getters无处不在实际上意味着)。
具体例子
想想容器中的元素访问通常是这样实现的。举个例子:
struct my_array {
int operator[](unsigned i) const { return data[i]; }
int& operator[](unsigned i) { return data[i]; }
private:
int data[10];
};
向用户隐藏元素不是容器的工作(甚至 data
也可能是 public)。您不希望根据您是要读取还是写入元素而使用不同的方法来访问元素,因此在这种情况下提供 const
和非常量重载非常有意义。
来自 get 与封装的非常量引用
可能不是那么明显,但是提供getters和setters是支持封装还是相反有点争议。虽然一般来说这个问题在很大程度上是基于意见的,但对于 return 非常量引用的 getter 来说,它与意见无关。他们确实打破了封装。考虑
struct broken {
void set(int x) {
counter++;
val = x;
}
int& get() { return x; }
int get() const { return x; }
private:
int counter = 0;
int value = 0;
};
这个class顾名思义就是坏了。客户端可以简单地获取一个引用,class 没有机会计算值被修改的次数(正如 set
所建议的)。一旦你 return 是一个非 const 引用,那么关于封装与使成员 public 几乎没有区别。因此,这仅用于这种行为是自然的情况(例如容器)。
PS
请注意,您的示例 return 是 const T&
而不是值。这对于模板代码来说是合理的,因为您不知道副本有多昂贵,而对于 int
,您不会通过 returning const int&
而不是 int
获得太多收益.为了清楚起见,我使用了非模板示例,但对于模板化代码,您可能更愿意 return a const T&
.
这取决于 S
的目的。如果它是某种薄包装器,允许用户直接访问底层值可能是合适的。
现实生活中的一个例子是 std::reference_wrapper
。
没有。如果 getter 只是 return 对成员的非常量引用,如下所示:
private:
Object m_member;
public:
Object &getMember() {
return m_member;
}
那么 m_member
应该是 public 而不是,不需要访问器。绝对没有必要将此成员设为私有,然后创建一个访问器,它为 all 提供访问权限。
如果你调用 getMember()
,你可以将结果引用存储到 pointer/reference,然后你可以用 m_member
做任何你想做的事,封闭的 class 会一无所知。是一样的,就好像 m_member
变成了 public.
注意,如果 getMember()
做一些额外的任务(例如,它不只是简单地 return m_member
,而是懒惰地构造它),那么 getMember()
可能有用:
Object &getMember() {
if (!m_member) m_member = new Object;
return *m_member;
}
首先让我重新表述一下你的问题:
Why have a non-const getter for a member, rather than just making the member public?
几个可能的原因原因:
1。易于检测
谁说非常量 getter 需要只是:
T& get() { return val; }
?它很可能是这样的:
T& get() {
if (check_for_something_bad()) {
throw std::runtime_error{
"Attempt to mutate val when bad things have happened");
}
return val;
}
但是,正如@BenVoigt 所建议的,更合适的做法是等到调用者实际尝试通过引用改变值,然后再发出错误。
2。文化公约/《老板这么说》
一些组织强制执行编码标准。这些编码标准有时是由可能过度防御的人编写的。因此,您可能会看到如下内容:
Unless your class is a "plain old data" type, no data members may be public. You may use getter methods for such non-public members as necessary.
然后,即使特定 class 只允许非常量访问是有意义的,它也不会发生。
3。也许 val
不存在?
您给出了一个示例,其中 val
实际上 存在于 class 的实例中。但实际上 - 它不必! get()
方法可以 return 某种 proxy object,它在分配、变异等时执行一些计算(例如,在数据库中存储或检索数据;或翻转一点,这本身不像对象需要的那样是可寻址的。
4。允许稍后更改 class 内部结构而不更改用户代码
现在,阅读上面的第 1 项或第 3 项,您可能会问“但是我的 struct S
有 val
!”或“我的 get()
没有做任何有趣的事情!” - 嗯,真的,他们没有;但您将来可能想更改此行为。如果没有 get()
,您 class' 的所有用户都需要更改他们的代码。使用 get()
,您只需更改 struct S
.
现在,我不提倡这种设计方法,但有些程序员提倡。