我应该返回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和返回值是什么呢, intconst 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是不可修改的,因此在使用intconst int没有可观察到的差异。 如果你使用了一个可以修改的更复杂的对象,你会看到一个区别。

围绕这两个版本的技术性有一些很好的答案。 对于一个原始的价值,它没有任何区别。

不过 ,我一直认为const在程序员而不是编译器中。 当你写const ,你明确地说“这不应该改变”。 让我们面对它吧,无论如何, const通常都可以被绕过,对吗?

当你返回一个const ,你告诉程序员使用该函数该值不应该改变。 如果他改变了,他可能做错了,因为他/她不应该这样做。

编辑:我也认为“尽可能使用const ”是不好的build议。 你应该在有意义的地方使用它。