我应该返回const对象吗?

时间:2012-08-21 08:36:00

标签: c++

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

12 个答案:

答案 0 :(得分:84)

忽略非类型返回类型的顶级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

答案 1 :(得分:35)

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

返回值

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

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

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

<强>参数

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

功能本身

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

<强>结论

如果您的方法没有并且永远不会修改类变量,请将其标记为const并确保满足所需的所有标准。它将允许更清晰的代码写入,从而保持 const-correct 。然而,将const放在任何地方而不给它任何想法肯定不是可行的方法。

答案 2 :(得分:27)

const资格添加到非参考/非指针右值, 没有点 em> 将其添加到内置插件中。

如果是 用户定义的类型 const资格认证将阻止来自 的来电者调用非{{1}返回的对象上的成员函数 。例如,给定

const

然后

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

将被禁止,而

foo().resize(42);

是允许的。

对于像bar().resize(4711); 这样的内置函数,这根本没有意义,因为无论如何都不能修改这些rvalues。

(我记得 Effective C ++ 正在讨论如何制作int operator=()引用的返回类型,这是值得考虑的事情。)


<强> 编辑:

似乎 Scott确实提供了这个建议 。如果是这样,那么由于上面给出的原因, 我发现即使对于C ++ 98和C ++ 03 也有问题。 对于C ++ 11,我认为这显然是错误的 ,正如Scott自己似乎已经发现的那样。在errata for Effective C++, 3rd ed.中,他写道(或引用其他抱怨的人):

  

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

后来:

  

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

答案 3 :(得分:17)

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

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

int& operator[](const int index)

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

const int operator[](const int index) const

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

在我个人看来,如果某个对象按值传递,则const修饰符是多余的。此语法更短,并实现相同的

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

答案 4 :(得分:13)

将值作为const返回的主要原因是您不能说foo() = 5;之类的内容。这实际上不是原始类型的问题,因为你不能分配给原始类型的rvalues,但它 是用户定义类型的问题(如(a + b) = c;,带有重载operator+)。

我总是觉得这样做的理由相当脆弱。你不能阻止那些打算编写尴尬代码的人,而这种特殊类型的强制行为在我看来并没有真正的好处。

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

这是关于C ++ 11的tangentially related article

答案 5 :(得分:9)

编写该书时,该建议几乎没用,但确实可以阻止用户编写,例如foo() = 42;,并希望它能够改变持久性。

对于operator[],如果您还没有提供非const重载来返回非const引用,这可能会有点混乱,尽管您可能也许通过返回const引用或代理对象而不是值来防止这种混淆。

现在,这是一个糟糕的建议,因为它会阻止您将结果绑定到(非const)右值参考。

(正如评论中指出的那样,在返回像int这样的原始类型时问题没有实际意义,因为语言会阻止你分配给这种类型的rvalue;我正在谈论关于更一般的情况,包括返回用户定义的类型。)

答案 6 :(得分:2)

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

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

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

如果bar()C的const成员函数,则禁止上述操作。 一般来说,选择合理的。

答案 7 :(得分:0)

看看:

const int operator[](const int index) const

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

另一方面,当你只写

int& operator[](const int index)

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

big_int[0] = 10;

语法。

答案 8 :(得分: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 `

答案 9 :(得分:0)

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

使用

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

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

mybigint[3] = 5;

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

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

答案 10 :(得分:0)

此处const返回类型并不那么重要。由于int temporaries不可修改,因此使用int vs const int时没有可观察到的差异。如果你使用了一个可以修改的更复杂的对象,你会发现不同。

答案 11 :(得分:0)

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

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

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

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