我应该返回const对象吗?


78

Effective C++项目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
很好的问题-尽管在这种情况下,方法在两种情况下通常都不会返回引用吗?
肯·韦恩·范德林德

好问题。我建议你把它改为int operator[](int i)const int operator[](const int i)
亚历山大·切尔托夫

1
@KenWayneVanderLinde,不。调用的方法取决于我们是否具有Bigint&a const Bigint&
亚历山大·切尔托夫

1
@AlexanderChertov否:他询问的是返回类型,而不是参数类型,因此问题应仅关注
于此

为什么?我个人想知道为什么有人会const int i吃些古怪的东西。
亚历山大·切尔托夫

Answers:


87

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

int const foo();

返回类型为int。如果返回类型是引用,则当然const不再是顶级,并且它们之间的区别是:

int& operator[]( int index );

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

是重要的。(也请注意,在函数声明中,像上面一样,所有顶级cv限定词也将被忽略。)

区别也与类类型的返回值有关:如果返回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

6
ff和gg被声明为返回Test的函数。如果是这样,则调用应为ff()。f()等...如果ff是Test类的实例,则应将括号从声明中删除(但应
加上

@ user396672(和BJ ...)是的。我忘记了()之后ffgg。我将其添加。谢谢你的纠正。
詹姆斯·坎泽

对于“区别也与类类型的返回值有关”之后的所有内容,+ 1。的应用const很有用,但是很多人似乎要么忽略了它,要么坚持认为返回const值没有任何作用。
underscore_d 2015年

4
-1 “忽略非类类型的返回类型的顶级cv限定词”是错误的。它们仍然是函数类型的一部分。但是,将忽略非类类型的prvalue的cv限定,因此int const,例如,返回return的函数调用等同于修改为return的那个函数的调用int。C ++ 11§3.10/ 4中的标准语言,“类prvalue可以具有cv限定的类型;非类prvalue始终具有cv不合格的类型”。
干杯和健康。-Alf's

38

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

返回值

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

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

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

参量

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

功能本身

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

结论

如果您的方法永远不会修改类变量,则将其标记为const并确保满足所有需要的条件。它将允许编写更简洁的代码,从而保持const-correct。但是,const无所不用其极地摆在任何地方肯定不是走的路。


4
进行两个更正:首先,对于按值返回,对于非类类型,const将忽略顶级。对于类类型确实很重要。对于按值传递的参数,在函数声明中将忽略顶层。它仅在定义中具有含义。const
詹姆斯·坎泽

如果您不介意,我会将这个评论纳入我的答案。我认为届时会更加清楚。
Bartek Banachewicz 2012年

至于const函数:它修改的类型this,即T const*,而不是T*。所有其他影响都源于此。(并且它并不能阻止对象的所有修改:mutable数据仍然可以更改,并且该函数可以合法地丢弃const并更改其想要的任何内容。)
James Kanze 2012年

“如果函数通过引用返回,它将保护返回的对象免遭修改” false
NoSenseEtAl

哎呀,我的意思是“,[const]保护对象”……我将其修复为使其更具可读性。
Bartek Banachewicz 2012年

30

没有什么价值的加入const资格的非参考/非指针右值,并没有一点在其添加到内置插件。

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

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

然后

foo().resize(42);

将被禁止,而

bar().resize(4711);

将被允许​​。

对于int诸如这样的内置函数,这根本没有任何意义,因为无论如何都无法修改此类右值。

(我记得C ++有效讨论作出的返回类型operator=()一个const参考,不过,这是值得考虑的问题。)


编辑:

看来斯科特确实给出了建议。如果是这样,则由于上述原因,即使对于C ++ 98和C ++ 03我也认为它值得怀疑对于C ++ 11,我认为这是完全错误的,因为Scott自己似乎已经发现了。在有效C ++勘误表中,第3版。,他写道(或引用其他抱怨的人):

文本暗示所有按值返回均应为const,但是非const按值返回是好的设计的情况并不难发现,例如,std :: vector的返回类型,其中调用方将使用带有空向量的swap “获取”返回值内容而不复制它们。

然后:

声明按值函数返回值const将防止它们绑定到C ++ 0x中的右值引用。由于右值引用旨在帮助提高C ++代码的效率,因此在指定函数签名时,必须考虑const返回值的交互作用和右值引用的初始化。


1
我喜欢在Scott的建议下提到勘误表。确实有助于阐明对有效C ++
初学者而言

2
@sehe:我给Scott寄了一封信,询问有关此问题的信息,他的答复是,这些勘误表述反映了他当前在该问题上的想法。
2012年

17

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

这是一个非const方法(const末尾为no ),这意味着可以修改类的状态。

int& operator[](const int index)

这是一个const方法(最后通知const

const int operator[](const int index) const

关于参数的类型和返回值,int和之间有细微的差别const int,但这与建议的要点无关。您应该注意的是,非const重载将返回int&,这意味着您可以为其分配值,例如num[i]=0,而const重载将返回不可修改的值(无论返回类型是int还是const int)。

我个人认为,如果对象通过传递,则const修饰符是多余的。此语法更短,并且达到相同的效果

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

5
实际上,问题在于返回const值。
juanchopanza 2012年

@juanchopanza:我怀疑OP就是这个意思。将他的foo示例与operator[]示例进行比较。他可能会忘记const方法的修饰符会有所作为,而不是返回类型会有所不同的观点。
安德烈(Andrey)2012年

13

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

我总是发现这种脆弱的理由。您无法阻止想要编写尴尬代码的人,而我认为这种特殊类型的强制性并没有真正的好处。

使用C ++ 11,此惯用语实际上造成了很多危害:将值返回为const会阻止移动优化,因此应尽可能避免。基本上,我现在将其视为反模式。

这是与C ++ 11相关切线相关文章


说修改返回值很棘手?像MyClass.isInitialized()一样。如果调用者更改了返回值,我将在代码审查中使用WTF。
NoSenseEtAl 2012年

@NoSenseEtAl:我是谁,知道毫无意义?就我们所知,赋值运算符可以修改全局状态,或订购披萨...
Kerrek SB 2012年

我添加了示例。对我过去的评论。而且您是该类的设计师,因此您可以了解返回类型:D的operator =。因此,您知道调用方更改您的方法结果是否毫无意义。
NoSenseEtAl 2012年

9

当写那本书时,建议没有什么用,但是确实起到了防止用户写作的作用,例如,foo() = 42;期望用户改变它的持久性。

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

如今,这是个坏建议,因为它会阻止您将结果绑定到(非const)右值引用。

(如评论中所指出的,返回诸如的原始类型时,这个问题是没有意义的int,因为该语言阻止您分配给rvalue这种类型的;我说的是更普遍的情况,其中包括返回用户定义的类型。 )


10
他的榜样又回来了intfoo() = 42;不管返回类型是声明int还是,都是非法的int const。我不认为Scott在争论const在函数声明中使用诸如返回类型和参数之类的地方,在该地方编译器会忽略它。
詹姆斯·坎泽

@JamesKanze:感谢您指出这一点;我应该说清楚我在说的更笼统。
Mike Seymour 2012年

2

对于基本类型(如int),结果的恒定性无关紧要。对于班级,它可能会改变行为。例如,您可能无法在函数的结果上调用非const方法:

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

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

如果bar()是的const成员函数,则禁止使用上述方法C。通常,选择任何有意义的方法。


0

看一下:

const int operator[](const int index) const

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

另一方面,当你只写

int& operator[](const int index)

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

big_int[0] = 10;

句法。


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 `

0

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

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

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

mybigint[3] = 5;

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

但是,作为函数的返回值,对于简单的类型,例如int,没关系。确实重要的是,如果要返回更复杂的类型,请说a std::vector,并且不希望函数的调用者修改向量。


0

const返回类型不是那么重要的位置。由于int临时变量不可修改,因此使用时没有明显的区别intvs与const int。如果您使用可以修改的更复杂的对象,则会看到不同。


0

关于这两个版本的技术性,有一些很好的答案。对于原始值,这没有什么区别。

但是,我一直认为const是程序员而不是编译器在那儿。当您编写时const,您明确地说“这不应该改变”。面对现实吧,const无论如何通常都可以被割礼吧?

当您返回时const,您是在告诉使用该函数的程序员该值不应更改。而且,如果他要更改它,则可能是他做错了,因为他/她不必这样做。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.