#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; }
输出:
原值:1
预期价值:3
实际值:4
显然,程序员被b的常量所欺骗.错误的b指向此,这会产生不良行为.
我的问题:在设计getter / setter时你应该遵循什么const规则?
我的建议:如果可以通过成员函数通过引用设置,则永远不要返回对成员变量的引用.因此,要么按值返回,要么按值传递参数. (现代编译器无论如何都会优化掉额外的副本.)
解决方法
ObvIoUsly,the programmer is fooled by the constness of 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; }
或者,如果您想确保该值是常量,请使用临时值
void Foo(B b) { m_B.increment_by(b); m_B.increment_by(b); }
现在,增加一个值本身可能是合理的,也可能是不合理的,并且很容易在B :: increment_by中进行测试.您还可以测试A :: Foo中的& m_b ==& b,但是一旦您有几个级别的对象和对象引用其他对象而不是值(因此& a1.bc ==& a2.bc并不意味着& a1.b ==& a2.b或& a1 ==& a2),那么你真的必须要知道任何操作都有可能是别名.
别名意味着通过表达式两次递增与第一次计算表达式时递增表达式的值不同;没有真正的方法,在大多数系统中,复制数据的成本不值得避免别名的风险.
传递结构最少的参数也很有效.如果Foo()花了一个长而不是一个它必须得到长的对象,那么它就不会遭受别名,你不需要写一个不同的Foo()来增加m_b的值.