【问题标题】:Should I return const objects?我应该返回 const 对象吗?
【发布时间】:2012-08-21 08:36:00
【问题描述】:

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

【问题讨论】:

  • 好问题 - 虽然在这种情况下,该方法通常不会在两种情况下都返回引用吗?
  • 好问题。我建议您将其更改为 int operator[](int i) 而不是 const int operator[](const int i)
  • @KenWayneVanderLinde,不。调用的方法取决于我们是否有Bigint&a const Bigint&
  • @AlexanderChertov no:他问的是返回类型,而不是参数类型,所以问题应该只关注这个
  • 为什么?我个人想知道为什么有人会有const int i 参数。

标签: c++


【解决方案1】:

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

int const foo();

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

int& operator[]( int index );

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

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

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

【讨论】:

  • ff 和 gg 被声明为返回 Test 的函数。如果是这样,则调用应该是 ff().f() 等...如果 ff 是 Test 类的实例,则应从声明中删除括号(但是 +1 :)
  • @user396672(和 BJ...)是的。我忘记了ffgg 之后的()。我会添加它们。谢谢指正。
  • −1 “忽略非类类型返回类型的顶级 cv 限定符”不正确。它们仍然是函数类型的一部分。但是,非类类型的纯右值的 cv 限定被忽略,因此调用返回 int const 的函数,相当于调用修改为返回 int 的函数。 C++11 §3.10/4 中的标准语言,“类纯右值可以具有 cv 限定类型;非类纯右值总是有 cv 非限定类型”。
【解决方案2】:

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

返回值

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

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

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

参数

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

函数本身

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

结论

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

【讨论】:

  • 两个更正:首先,对于按值返回,顶级const 被忽略对于非类类型。这对类类型很重要。而对于按值传递的参数,顶级const 在函数声明中被忽略。它只在定义中有意义。
  • 如果您不介意,我会将此评论纳入我的回答中。我想这会更清楚。
  • 对于函数上的const:它修改了this的类型,即T const*,而不是T*。所有其他影响都源于此。 (并且它不会阻止对对象的所有修改:mutable 数据仍然可以更改,并且该函数可以合法地抛弃 const,并更改它想要的任何东西。)
【解决方案3】:

const 限定符添加到非引用/非指针右值价值不大,并且没有意义 将其添加到内置插件中。

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

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

然后

foo().resize(42);

会被禁止,而

bar().resize(4711);

将被允许。

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

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


编辑:

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

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

后来:

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

【讨论】:

  • 我喜欢在 Scott 的建议中提到勘误表。确实有助于阐明对 Effective C++ 新手可能会感到困惑的地方
  • @sehe:我给 Scott 发了一封邮件询问这个问题,他的回复是这些勘误表反映了他目前对这个问题的想法。
【解决方案4】:

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

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

int& operator[](const int index)

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

const int operator[](const int index) const

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

在我个人看来,如果一个对象按值传递const 修饰符是多余的。这种语法更短,实现的效果一样

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

【讨论】:

  • 其实问题是关于返回一个const的值。
  • @juanchopanza:我怀疑 OP 就是这个意思。将他的foo 示例与operator[] 示例进行比较。他可能会忽略 const 修饰符对方法的影响,而不是返回类型。
【解决方案5】:

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

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

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

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

【讨论】:

  • 说修改返回值没有意义呢?像 MyClass.isInitialized()。如果调用者更改返回值,我会在代码审查中进行 WTF。
  • @NoSenseEtAl:我是谁知道什么是无意义的?据我们所知,赋值运算符可以修改全局状态,或者订购披萨......
  • 我添加了示例。对我过去的评论。而且您是该课程的设计者,因此您了解返回类型的 operator = :D。所以你知道调用者改变你的方法的结果是否没有意义。
【解决方案6】:

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

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

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

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

【讨论】:

  • 他的示例返回了intfoo() = 42; 是非法的,无论返回类型声明为int 还是int const。我不认为 Scott 主张在函数声明中的返回类型和参数等位置使用 const,编译器会忽略它。
  • @JamesKanze:感谢您指出这一点;我应该说清楚,我说的更笼统。
【解决方案7】:

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

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

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

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

【讨论】:

  • 不应该是“如果 bar() 不是 const 成员...”吗?
  • @ShuvoSarker,你当然是对的,固定的。
【解决方案8】:

看那个:

const int operator[](const int index) const

语句末尾的const。它描述了这个方法可以在常量上调用。

另一方面,当你只写时

int& operator[](const int index)

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

big_int[0] = 10;

语法。

【讨论】:

    【解决方案9】:

    您的一个重载是返回一个 reference 到数组中的某个项目,然后您可以更改它。

    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 `
    

    【讨论】:

      【解决方案10】:

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

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

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

      mybigint[3] = 5;
      

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

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

      【讨论】:

        【解决方案11】:

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

        【讨论】:

          【解决方案12】:

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

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

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-03
            • 1970-01-01
            • 2017-04-07
            • 2020-08-26
            • 2010-09-11
            相关资源
            最近更新 更多