79

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

4

12 回答 12

90

非类类型的返回类型的顶级 cv 限定符被忽略。这意味着即使你写:

int const foo();

返回类型是int. 如果返回类型是引用,当然const就不再是顶层了,区别如下:

int& operator[]( int index );

int const& operator[]( int index ) const;

很重要。(还要注意,在函数声明中,像上面一样,任何顶级 cv 限定符也被忽略。)

区别也与类类型的返回值有关:如果你 return T 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
于 2012-08-21T08:50:42.853 回答
40

您应该清楚地区分应用于返回值、参数和函数本身的 const 用法。

返回值

  • 如果函数按 value返回,则 const 无关紧要,因为正在返回对象的副本。然而,它在涉及移动语义的 C++11 中很重要。
  • 它对基本类型也无关紧要,因为它们总是被复制的。

考虑const std::string SomeMethod() const。它不允许使用该(std::string&&)函数,因为它需要非常量右值。换句话说,返回的字符串将始终被复制。

  • 如果函数通过引用返回,const则保护返回的对象不被修改。

参数

  • 如果按值传递参数,则 const 会阻止函数中的函数修改给定值。无论如何都无法修改参数的原始数据,因为您只有副本。
  • 请注意,由于始终创建副本,因此 const 仅对函数体有意义,因此,仅在函数定义中检查它,而不是在声明(接口)中检查。
  • 如果通过引用传递参数,则适用与返回值相同的规则

函数本身

  • 如果函数const末尾有,则只能运行其他const函数,不能修改或允许修改类数据。因此,如果它通过引用返回,则返回的引用必须是 const。只能在对象上调用 const 函数或引用本身为 const 的对象。可变字段也可以更改。
  • 编译器创建的行为将this引用更改为T const*. 函数 can always const_cast this,但当然不应该这样做并且被认为是不安全的。
  • 只在类方法上使用这个说明符当然是明智的;最后带有 const 的全局函数会引发编译错误。

结论

如果您的方法不会也永远不会修改类变量,请将其标记为 const 并确保满足所需的所有条件。它将允许编写更清晰的代码,从而使其保持const-correct。然而,不加思索const地到处放置肯定不是要走的路。

于 2012-08-21T08:50:18.463 回答
31

将限定条件添加到非引用/非指针右值几乎没有价值,将其添加到内置函数也没有意义。const

用户定义类型的情况下,const限定将阻止调用者在返回的对象上调用非const成员函数。例如,给定

const std::string foo();
      std::string bar();

然后

foo().resize(42);

将被禁止,而

bar().resize(4711);

将被允许​​。

对于像这样的int内置函数,这根本没有意义,因为这样的右值无论如何都不能被修改。

(不过,我确实记得Effective C++讨论过创建引用的返回类型operator=()const这是需要考虑的事情。)


编辑:

看来斯科特确实给出了这个建议。如果是这样,那么由于上述原因,我发现即使对于 C++98 和 C++03 也是有问题的对于 C++11,我认为它显然是错误的,正如 Scott 本人似乎已经发现的那样。在Effective C++ 第 3 版的勘误表中。,他写道(或引用其他抱怨的人):

文本暗示所有按值返回都应该是 const,但不难找到非常量按值返回是好的设计的情况,例如 std::vector 的返回类型,其中调用者将使用带有空向量的交换“抓取”返回值内容而不复制它们。

然后:

声明按值函数返回值 const 将防止它们被绑定到 C++0x 中的右值引用。因为右值引用旨在帮助提高 C++ 代码的效率,所以在指定函数签名时考虑 const 返回值的交互和右值引用的初始化非常重要。

于 2012-08-21T09:03:37.207 回答
18

你可能会错过迈耶斯的建议。本质区别在于const方法的修饰符。

这是一个非常量方法(const最后没有注意),这意味着它可以修改类的状态。

int& operator[](const int index)

这是一个 const 方法(const最后注意)

const int operator[](const int index) const

参数和返回值的类型呢,和之间有细微的差别intconst int但与建议的点无关。您应该注意的是,非常量重载返回int&意味着您可以分配给它,例如num[i]=0,并且 const 重载返回不可修改的值(无论返回类型是intor const int)。

在我个人看来,如果一个对象是按值传递的,那么const修饰符是多余的。这种语法更短并且达到相同的效果

int& operator[](int index);
int operator[](int index) const;
于 2012-08-21T08:40:10.190 回答
15

将值返回为 const 的主要原因是您不能说类似foo() = 5;. 这实际上不是原始类型的问题,因为您不能分配给原始类型的右值,但这用户定义类型的问题(例如(a + b) = c;,重载的operator+)。

我总是为这种相当脆弱的理由找到理由。您无法阻止打算编写笨拙代码的人,而这种特殊类型的强制在我看来并没有真正的好处。

对于 C++11,这个习惯用法实际上有很多危害:将值返回为 const 会阻止移动优化,因此应尽可能避免。基本上,我现在认为这是一种反模式。

这是一篇与 C++11 相关的文章。

于 2012-08-21T08:44:00.077 回答
9

当那本书写成时,这个建议几乎没有用,但确实起到了防止用户写作的作用,例如,foo() = 42;并期望它改变一些持久的东西。

在 的情况下operator[],如果您不提供const返回非引用的非重载,这可能会有些混乱,尽管您可以通过返回引用或代理对象而不是值来const防止这种混淆。const

这些天来,这是一个不好的建议,因为它会阻止您将结果绑定到(非const)右值引用。

(正如评论中所指出的,当返回一个原始类型时这个问题是没有实际意义的int,因为该语言阻止你分配一个rvalue这样的类型;我说的是更一般的情况,包括返回用户定义的类型。 )

于 2012-08-21T08:40:51.860 回答
2

对于原始类型(如int),结果的常量性无关紧要。对于类,它可能会改变行为。例如,您可能无法对函数的结果调用非常量方法:

class Bigint {
    const C foo() const { ... }
     ...
}

Bigint b;
b.foo().bar();

bar()如果不是 的 const 成员函数,则禁止上述内容C。一般来说,选择任何有意义的。

于 2012-08-21T08:42:03.997 回答
0

看那个:

const int operator[](const int index) const

声明的const结尾。它描述了可以在常量上调用此方法。

另一方面,当你只写

int& operator[](const int index)

它只能在非常量实例上调用,并且还提供:

big_int[0] = 10;

句法。

于 2012-08-21T08:42:02.637 回答
0

您的重载之一是返回对数组中某个项目的引用,然后您可以对其进行更改。

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 `
于 2012-08-21T08:42:07.260 回答
0

在数组索引运算符 ( operator[]) 的示例中,它确实有所作为。

int& operator[](const int index) { /* ... */ }

您可以使用索引直接更改数组中的条目,例如像这样使用它:

mybigint[3] = 5;

第二个,const int operator[](const int)运算符仅用于获取值。

但是,作为函数的返回值,对于简单的类型,例如int,它并不重要。重要的是您是否返回更复杂的类型,比如 a std::vector,并且不希望函数的调用者修改向量。

于 2012-08-21T08:42:20.330 回答
0

返回类型在const这里并不那么重要。int由于 int 临时对象不可修改,因此使用vs没有明显的区别const int。如果您使用可以修改的更复杂的对象,您会看到不同之处。

于 2012-08-21T08:47:03.280 回答
0

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

但是,我一直认为const是为程序员而不是为编译器而存在。当你写的时候const,你明确地说“这不应该改变”。让我们面对现实吧,const无论如何通常都可以被规避,对吧?

当您返回 aconst时,您是在告诉使用该函数的程序员该值不应更改。如果他正在改变它,他可能做错了什么,因为他/她不应该这样做。

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

于 2012-08-21T09:01:25.220 回答