C++ 返回值、引用、常量引用

88

你能解释一下返回值、值的引用和值的常量引用之间的区别吗?

值:

Vector2D operator += (const Vector2D& vector)
{
    this->x += vector.x;
    this->y += vector.y;
    return *this;
}

非常量引用:

Vector2D& operator += (const Vector2D& vector)
{
    this->x += vector.x;
    this->y += vector.y;
    return *this;
}

常量引用:

const Vector2D& operator += (const Vector2D& vector)
{
    this->x += vector.x;
    this->y += vector.y;
    return *this;
}

这有什么好处呢?我理解传递const引用到函数的意义是你想确保在函数内部不修改引用所指向的值。但是我对返回const引用的含义感到困惑。为什么返回引用比返回值更好,为什么返回const引用比返回非const引用更好?


从运算符返回非常量引用的一个好处是运算符链。 - 0xB00B
5个回答

49

除非您编写了一些奇怪的东西,否则不会有任何区别。

(v1 += v2) = v3;

第一种情况,会分配给一个临时变量,总体效果将是v1+=v2

第二种情况,会分配到v1,所以总体效果将是v1=v3

第三种情况,不允许分配。这可能是最好的选择,因为这种奇怪的情况几乎肯定是个错误。

为什么返回引用比返回值更好?

这潜在地更有效率:你不需要拷贝对象。

为什么返回const引用比返回非const引用更好?

你能防止像上面那个例子一样的异常,同时仍然允许不那么奇怪的链接,如

v1 = (v2 += v3);

但是需要注意的是,正如评论中所指出的那样,这意味着您的类型不支持与内置类型相同的(滥用)形式,而有些人认为这是可取的。


2
我认为const是否比非const更好还有争议,你不能在+=的结果上调用一个非const的方法。那么(v1 += v2).non_const_fun();呢?我知道这是个人观点,也承认它看起来有点奇怪,但它确实比(v1+=v2)=v3更有意义。真的有必要禁止它吗? - luk32
4
我可以理解禁止使用+=的逻辑,但是... 内置的 += 返回一个可修改的左值(lvalue),并且返回非常量引用是用户定义类型中最接近此特性的方法。因此,“最少惊奇原则”——用户定义的运算符应该模拟内置运算符——建议使用非常量引用。 - James Kanze
1
实际上,对于应该允许哪种多效表达式存在不同的意见。我可能会返回“void”,以防止任何意外情况,除非有特定要求允许混淆。 - Mike Seymour
@JamesKanze 这是我在回答中试图表达的一个非常好的观点。这种模拟示例 (v1+=v2)=v3 的语法是可以正常工作的。至少,我期望它能够正常工作,因为对于内置类型来说是有效的。我只是想知道为什么要禁止这样做。难道只是为了可能捕捉到“愚蠢”的错误吗? - luk32
只是为了明确,我认为可以更改预期/允许的语法是一个非常好的观点。这可能是值得的。我认为这是一种“权衡”。我征求意见,因为你们(所有评论者)比我拥有更丰富的经验。 - luk32
显示剩余2条评论

22
值: 返回值意味着你返回了一个对象的副本。这要求类具备可复制或可移动特性。这也意味着对于某些类的对象来说,通过值返回可能是昂贵的(在RVO或NRVO不能工作或关闭的情况下)。这也意味着新对象独立于其他对象(取决于其设计)且是自身的值。这是您可能应该从许多二元运算符(如+、-、*等)返回的东西。 非 const 引用: 实际上,您返回的是另一个对象的别名。这个别名是非 const 的,允许您修改别名对象。这是您应该从一些一元运算符(如前缀++和--以及*(引用))返回的内容,因为通常您希望能够修改返回的对象。
这是operator>>和operator<<为流重载的返回值。 这允许操作符的链接:
cout << 5 << "is greater then" << 1 << endl;
cin >> myInt >> myFloat;

当您想要允许正常方法链接时,也可以返回对*this的引用,例如:

object.run().printLastRunStatistics();

常量引用:

与上面相似,但你不能修改别名对象。当要返回的对象复制开销较大且返回函数后可以确保其存在时,可用于代替按值返回。

这通常是operator= 返回的内容,以支持标准类型所支持的多重赋值方式:

a = b = c;

在operator=中使用const引用会防止这种类型的使用(据我所记,标准类型不支持):

++(a = b);

如果使用常规引用,这将是允许的。


15
按值返回按引用返回的区别在运行时发生作用:
当按值返回对象时,拷贝构造函数被调用,并在堆栈上创建临时实例。
当按引用返回对象时,不会发生上述任何情况,从而提高了性能。
按引用返回按常量引用返回之间的区别没有运行时效果,它只是为了防止编写错误的代码。
例如,使用Vector2D& operator += (const Vector2D& vector),您可以执行以下操作: (x+=y)++(x+=y).func()其中func是类Vector2D中的非常量函数。
但是使用const Vector2D& operator += (const Vector2D& vector),编译器将为任何类似的尝试生成错误。

6

这与向函数传递参数完全相同。

当您返回对象的属性时,希望该属性在外部不被修改,应返回一个const引用。例如:当您的对象具有名称时,可以创建以下方法:const std::string& get_name(){ return name; }。这是最优的方法。您允许“只读”访问内部属性,而无需复制返回。

当您重载运算符时,应返回一个可变对象,否则通常预期的某些语法将导致错误。当您尝试进行一些奇怪的链接时,这非常重要。

例如,选项3将无法使用像(v1 += v2).non_const_method()这样的内容,而以下内容可以:

v1+=v2;
v1.non_const_method();

1
好的,对我来说这并不清楚。所以“const”直接影响返回对象,因此我们无法在其上调用级联函数。如果我们将返回的对象分配给我们的新对象,则它不再是const。感谢你们所有人! - Majak

1
正如luk32所指出的,这只是为了确保不允许修改此函数返回的对象。这基本上可以帮助您在编译时找到逻辑错误。 假设您确定不更改对象,但代码正在更改对象,则可以跟踪它。它可以被视为良好的编码实践。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接