l33*_*33t 6 c++ const reference
考虑下面的示例应用程序.它展示了我称之为有缺陷的类设计.
#include <iostream>
using namespace std;
struct B
{
B() : m_value(1) {}
long m_value;
};
struct A
{
const B& GetB() const { return m_B; }
void Foo(const B &b)
{
// assert(this != &b);
m_B.m_value += b.m_value;
m_B.m_value += b.m_value;
}
protected:
B m_B;
};
int main(int argc, char* argv[])
{
A a;
cout << "Original value: " << a.GetB().m_value << endl;
cout << "Expected value: 3" << endl;
a.Foo(a.GetB());
cout << "Actual value: " << a.GetB().m_value << endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
原始值:1
预期值:3
实际值:4
显然,程序员被常量的愚弄b.错误地b指向this,这会产生不希望的行为.
我的问题:在设计getter/setter时你应该遵循什么const规则?
我的建议:如果可以通过成员函数通过引用设置,则永远不要返回对成员变量的引用.因此,无论是通过返回值或传递参数通过值.(现代编译器无论如何都会优化掉额外的副本.)
Pet*_*ham 20
显然,程序员被b的常量所欺骗
正如有人曾经说过的那样,你继续使用那个词.我认为这并不意味着你的意思.
Const意味着您无法更改该值.这并不意味着价值不能改变.
如果程序员被一些其他代码可以改变他们无法改变的事实所迷惑,那么他们需要更好的基于别名的基础.
如果程序员被令牌"const"听起来有点像"常量"但意味着"只读"这一事实所迷惑,那么他们需要在他们正在使用的编程语言的语义上有更好的基础.
因此,如果您有一个返回const引用的getter,那么它是您无权更改的对象的别名.这没有说明它的价值是否是不可改变的.
最终,这归结为缺乏封装,而不是应用得墨忒耳定律.通常,不要改变其他对象的状态.向他们发送消息,要求他们执行操作,这可能(取决于他们自己的实现细节)改变他们的状态.
如果你B.m_value私有化,那你就不能写出Foo你拥有的.你要么Foo:
void Foo(const B &b)
{
m_B.increment_by(b);
m_B.increment_by(b);
}
void B::increment_by (const B& b)
{
// assert ( this != &b ) if you like
m_value += b.m_value;
}
Run Code Online (Sandbox Code Playgroud)
或者,如果您想确保该值是常量,请使用临时值
void Foo(B b)
{
m_B.increment_by(b);
m_B.increment_by(b);
}
Run Code Online (Sandbox Code Playgroud)
现在,增加一个值本身可能是合理的,也可能是不合理的,并且很容易在B :: increment_by中进行测试.您还可以测试是否&m_b==&b在A :: Foo中,虽然一旦您有几个级别的对象和对象引用其他对象而不是值(因此&a1.b.c == &a2.b.c并不意味着&a1.b== &a2.b或&a1== &a2),那么您真的必须请注意,任何操作都可能存在别名.
别名意味着通过表达式两次递增与第一次计算表达式时递增表达式的值不同; 没有真正的方法,在大多数系统中,复制数据的成本不值得避免别名的风险.
传递结构最少的参数也很有效.如果Foo()花了一个长而不是一个它必须得到长的对象,那么它就不会遭受别名,你不需要写一个不同的Foo()来增加m_b的值.