我正在与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和一个非const成员函数以获取val
。
我想知道:在任何情况下,让非常量get
函数返回val
有意义吗?
在我看来,在这种情况下我们不能间接改变价值,这更加清洁。在某些情况下,您需要一个const和一个非const get
函数来返回一个成员变量?
答案 0 :(得分:15)
非常量获取器?
字母和二传手仅仅是惯例。有时候提供一个习惯用法,而不是提供一个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&
};
总体而言,这是否是一种好风格是一个意见问题。在某些情况下会引起混乱,而在其他情况下,这种行为正是您所期望的(见下文)。
无论如何,更重要的是根据类的用途和使用方式来设计类的接口,而不是盲目地遵循有关setter和getter的约定(例如,应提供方法一个有意义的名字来表达它的作用,而不仅仅是“假装被封装,现在让我通过吸气剂访问所有内部组件”,这就是在各处使用吸气剂的真正含义。
具体示例
考虑到容器中元素的访问通常是这样实现的。作为玩具示例:
struct my_array {
int operator[](unsigned i) const { return data[i]; }
int& operator[](unsigned i) { return data[i]; }
private:
int data[10];
};
容器的工作不是向用户隐藏元素(即使data
也可以是公共的)。您不希望根据要读取或写入元素的方式来使用不同的方法来访问元素,因此在这种情况下提供const
和非常量重载是很有意义的。
get与封装中的非常量引用
也许不是那么明显,但是是否提供getter和setter支持封装还是相反则有点争议。虽然一般而言,此问题在很大程度上是基于意见的,但对于返回非const引用的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;
};
顾名思义,该类已损坏。客户可以简单地获取一个引用,而该类没有机会计算修改值的次数(如set
所建议的那样)。一旦返回非常量引用,则有关封装的信息与公开成员几乎没有什么区别。因此,这仅用于这种行为是自然的情况(例如容器)。
PS
请注意,您的示例返回一个const T&
而不是一个值。这对于模板代码是合理的,因为在模板代码中您不知道副本的价格,而对于int
,则返回const int&
而不是int
不会带来太多收益。为了清楚起见,我使用了非模板示例,尽管对于模板代码,您可能宁愿返回const T&
。
答案 1 :(得分:8)
首先让我改一下您的问题:
为什么要为成员使用非常量获取方法,而不仅仅是使成员公开?
几个可能的原因:
谁说非常量获取器必须是:
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;
}
However, as @BenVoigt suggests, it is more appropriate to wait until the caller actually tries to mutate the value through the reference before spewing an error.
一些组织执行编码标准。这些编码标准有时是由可能过分防御的人编写的。因此,您可能会看到类似的内容:
除非您的课程是"plain old data" type,否则任何数据成员都不得公开。您可以根据需要为此类非公开成员使用getter方法。
然后,即使特定类只允许非常量访问是有意义的,也不会发生。
val
不在吗?您已经给出了一个示例,其中val
实际上在类的实例中存在。但实际上-不必! get()
方法可以返回某种代理对象,该代理对象在进行分配,变异等操作后会执行一些计算(例如,在数据库中存储或检索数据)。
现在,请阅读上面的项目1或3,您可能会问“但是我的struct S
确实有val
!”或“根据我的get()
并没有做任何有趣的事!” -好吧,是的,他们没有;但是您将来可能希望更改此行为。没有get()
,您班的所有用户都将需要更改其代码。使用get()
,您只需更改struct S
的实现。
现在,我不提倡这种设计方法,但是有些程序员支持。
答案 2 :(得分:5)
get()
被允许进行突变的非const对象调用,您可以这样做:
S r(0);
r.get() = 1;
但是如果您将r
的常量设为const S r(0)
,则r.get() = 1
行将不再编译,甚至无法检索值,这就是为什么需要const版本const T& get() const
的原因至少能够检索const对象的值,这样做可以做到:
const S r(0)
int val = r.get()
成员函数的const版本尝试与对其进行调用的对象的 constness 属性保持一致,即,如果对象是const而不可变,并且成员函数返回引用,它可以通过返回const引用来反映调用方的 constness ,从而保留了对象的不变性。
答案 3 :(得分:3)
这取决于return "http://somewhere.com".getUrlText()
的目的。如果是某种薄包装纸,则允许用户直接访问基础值可能是合适的。
真实的例子之一是std::reference_wrapper
。
答案 4 :(得分:1)
不。如果getter只是返回对成员的非常量引用,如下所示:
private:
Object m_member;
public:
Object &getMember() {
return m_member;
}
然后m_member
应该是公共的,并且不需要访问器。绝对没有必要将此成员设为私有,然后创建一个访问器,该访问器授予全部访问权限。
如果调用getMember()
,则可以将结果引用存储到指针/引用,然后,您可以使用m_member
做任何您想做的事情,封闭的类对此一无所知。就像m_member
公开一样。
请注意,如果getMember()
做一些额外的任务(例如,它不仅返回m_member
,而是懒散地构造它),那么getMember()
可能会有用:< / p>
Object &getMember() {
if (!m_member) m_member = new Object;
return *m_member;
}