我应该返回const对象吗?
在Effective C++
Item 03中,尽可能使用const。
class Bigint { int _data[MAXLEN]; //... public: int& operator[](const int index) { return _data[index]; } const int operator[](const int index) const { return _data[index]; } //... };
const int operator[]
确实与int& operator[]
有所不同。
但是怎么样:
int foo() { }
和
const int foo() { }
看起来他们是一样的。
我的问题是,为什么我们使用const int operator[](const int index) const
而不是int operator[](const int index) const
?
忽略非types返回types的顶级cv限定符。 这意味着,即使你写:
int const foo();
返回types是int
。 如果返回types是一个引用,当然const
不再是顶层,而区分:
int& operator[]( int index );
和
int const& operator[]( int index ) const;
意义重大。 (还要注意,在函数声明中,像上面那样,任何顶级的cv-qualifiers也被忽略。)
这个区别对类types的返回值也是相关的:如果你返回T const
,那么调用者不能在返回的值上调用非const函数,例如:
class Test { public: void f(); void g() const; }; Test ff(); Test const gg(); ff().f(); // legal ff().g(); // legal gg().f(); // **illegal** gg().g(); // legal
你应该清楚地区分应用于返回值,参数和函数本身的const用法。
返回值
- 如果函数按值返回,那么const不会影响对象的副本。 然而,在C ++ 11中涉及到移动语义。
- 对于基本types,它也永远不会影响,因为它们总是被复制。
考虑const std::string SomeMethod() const
。 它不会允许使用(std::string&&)
函数,因为它需要非常量右值。 换句话说,返回的string将始终被复制。
- 如果该函数通过引用返回,则
const
保护返回的对象不被修改。
参数
- 如果按值传递一个参数,则const可以防止函数中函数对给定值的修改。 原来的参数数据无法修改,因为您只有副本。
- 请注意,由于始终创build副本,因此const仅对函数主体有意义,因此仅在函数定义中检查,而不在声明(接口)中检查。
- 如果通过引用传递参数,则与返回值中的规则相同
function本身
- 如果函数最后有
const
,则只能运行其他const
函数,不能修改或允许修改类数据。 因此,如果它通过引用返回,则返回的引用必须是const。 只有const函数可以在对象上引用或引用const对象本身。 此外,可变字段可以更改。 - 编译器创build的行为
this
引用更改为T const*
。 该函数总是可以const_cast
this
,但当然这不应该被做,并被认为是不安全的。 - 在类方法中只使用这个说明符当然是明智的。 具有const的全局函数最终会增加编译错误。
结论
如果你的方法没有,也不会修改类variables,把它标记为const,并确保满足所有需要的critieria。 它将允许编写更清晰的代码,从而保持它的正确性 。 但是,把const
放在一边,一点也不想,肯定不是要走的路。
将const
限定添加到非引用/非指针右值没有什么价值 ,也没有必要将其添加到内置函数中。
在用户定义types的情况下, const
限定将阻止调用者在返回的对象上调用非const
成员函数 。 例如,给出
const std::string foo(); std::string bar();
然后
foo().resize(42);
将被禁止,而
bar().resize(4711);
将被允许。
对于像int
这样的内置函数,这是毫无意义的,因为这样的rvalues不能被修改。
(我记得有效的C ++讨论使得operator=()
的返回types是一个const
引用,但是这是需要考虑的。)
编辑:
斯科特似乎确实给了这个build议 。 如果是这样,那么由于上面给出的原因, 我发现即使对于C ++ 98和C ++ 03也是有问题的 。 对于C ++ 11,我认为这是明显错误的 ,正如Scott自己似乎已经发现的那样。 在Effective C ++的勘误表中,第3版。 ,他写道(或引用别人抱怨):
该文本意味着所有的按值返回应该是const的,但是非const的按值返回是好devise的情况不难find,例如,返回types的std :: vector,其中调用者将使用交换空向量以“抓取”返回值内容而不复制它们。
然后:
声明值types函数的返回值const将阻止它们绑定到C ++ 0x中的右值引用。 由于右值引用旨在帮助提高C ++代码的效率,因此在指定函数签名时,必须考虑常量返回值的交互和右值引用的初始化。
你可能会错过迈耶斯的build议。 本质区别在于该方法的const
修饰符。
这是一个非const方法(注意在最后没有const
),这意味着它可以修改类的状态。
int& operator[](const int index)
这是一个const方法(最后注意const
)
const int operator[](const int index) const
那么参数的types和返回值是什么呢, int
和const int
之间有一点小小的区别,但是它与build议的重点没有关系。 你应该注意的是,非const重载返回int&
这意味着你可以分配给它,例如num[i]=0
,并且const重载返回不可修改的值(不pipe返回types是int
还是const int
)。
在我个人看来,如果一个对象是按值传递的 , const
修饰符是多余的。 这个语法更短,达到相同的效果
int& operator[](int index); int operator[](int index) const;
返回值为const的主要原因是你不能说像foo() = 5;
。 这对于原始types来说实际上并不是问题,因为你不能分配原始types的右值,但是用户定义的types(比如(a + b) = c;
和重载的operator+
) 是一个问题。
我一直觉得这个理由相当脆弱。 你不能阻止一个有意编写尴尬代码的人,而这种特殊的强制方式在我看来并没有真正的好处。
在C ++ 11中,这个习惯用法实际上有很多伤害 :以const值返回值可以防止移动优化,因此应尽可能地避免。 基本上,我现在认为这是一个反模式。
这里是一个关于C ++ 11的切线相关的文章 。
当这本书写的时候,这个build议没什么用处,但是阻止了用户的写作,例如foo() = 42;
并期待它改变一些持久的东西。
在operator[]
的情况下,如果你不提供一个返回一个非const
引用的非const
重载,这可能会有些混乱,尽pipe你可以通过返回一个const
引用或者一个代理对象来防止混淆的价值。
这些日子,这是不好的build议,因为它阻止你将结果绑定到(非const
)右值引用。
(正如在注释中指出的那样,当返回像int
这样的原始types时,问题是没有意义的,因为语言会阻止你将其赋值给这样一个types的rvalue
;我所谈论的是更一般的情况,包括返回用户定义types。)
对于原始types(如int
),结果的常量无关紧要。 对于类,它可能会改变行为。 例如,对于函数的结果,您可能无法调用非const方法:
class Bigint { const C foo() const { ... } ... } Bigint b; b.foo().bar();
如果bar()
是C
一个const成员函数,则禁止上述操作。 一般来说,select哪一个是有道理的。
看这个:
const int operator[](const int index) const
声明结束的const
。 它描述了这个方法可以在常量上调用。
另一方面,当你只写
int& operator[](const int index)
它只能在非常量实例上调用,并且还提供:
big_int[0] = 10;
句法。
你的重载之一是返回数组中的一个项目的引用 ,然后你可以改变。
int& operator[](const int index) { return _data[index]; }
另一个重载正在返回一个值供您使用。
const int operator[](const int index) const { return _data[index]; }
既然你以相同的方式调用了每一个重载,而且在使用的时候永远不会改变它的值。
int foo = myBigInt[1]; // Doesn't change values inside the object. myBigInt[1] = 2; // Assigns a new value at index `
在数组索引操作符( operator[]
)的例子中,它确实有所作为。
同
int& operator[](const int index) { /* ... */ }
你可以使用索引直接改变数组中的条目,例如像这样使用它:
mybigint[3] = 5;
第二个, const int operator[](const int)
操作符仅用于获取该值。
但是,作为函数的返回值,对于简单的types,比如int
这并不重要。 如果你要返回更复杂的types,比如std::vector
,并且不希望函数的调用者修改向量。
const
返回types在这里并不重要。 由于int临时variables是不可修改的,因此在使用int
和const int
没有可观察到的差异。 如果你使用了一个可以修改的更复杂的对象,你会看到一个区别。
围绕这两个版本的技术性有一些很好的答案。 对于一个原始的价值,它没有任何区别。
不过 ,我一直认为const
在程序员而不是编译器中。 当你写const
,你明确地说“这不应该改变”。 让我们面对它吧,无论如何, const
通常都可以被绕过,对吗?
当你返回一个const
,你告诉程序员使用该函数该值不应该改变。 如果他改变了,他可能做错了,因为他/她不应该这样做。
编辑:我也认为“尽可能使用const
”是不好的build议。 你应该在有意义的地方使用它。