1

由于按值返回在大多数情况下比返回引用效率低,有哪些按值返回有意义或者是唯一可能的方法的例子?

编辑:我错误地表达了我的问题。我知道对于内置类型,按值返回通常更有效。我主要指的是返回大于指针的用户定义类型的情况。

4

7 回答 7

7

你在这里遗漏了重点——通过引用返回并不总是有意义的,也不总是合法的。通过引用返回本地会导致未定义的行为。您可以通过引用返回一个超出函数范围的变量,它可以是:

  • 动态分配 - 坏主意,你必须自己管理生命周期
  • 类的成员 - 这通常与具有 2 个版本的 getter 一起使用:一个返回引用,您可以修改成员或返回引用的constgetterconst
  • 一个static局部变量(或任何具有静态存储的变量)——通常情况下,仅在通过引用返回的函数中不需要它。

NRVO 使按价值回报在实践中可行,因此除非您有明确的测量结果表明按价值回报是瓶颈,否则不要担心。而是担心两者所暗示的语义。

于 2013-08-25T16:25:15.793 回答
3

在两种情况下,按值返回的效率并不低: 1如果您正在移动对象 (C++11),2如果编译器可以删除副本或优化返回值(对于内置类型总是可能的,请参阅还有 JaredPar 的回答)。

几个优点1在函数调用之前不需要声明变量(这可能并不总是可能的),并且可以通过auto 2检测到返回的变量类型设置返回变量的意图是显而易见的。

void foo(some_type &obj_ref);
some_type bar();

// using reference
some_type A;          // requires default constructor for A
foo(A);               // modification of A is not obvious

// returning a value
auto B = bar();       // move or copy construction of B

请注意,最后一条语句可以使用返回值优化或移动语义 (C++11)

于 2013-08-25T16:17:47.250 回答
3

这是 g++ 在将 -O3 标志用于通过引用返回整数的方法时默认生成的:

void Foo(int& rc) {
    rc = 42;
}

变成:

__Z3FooRi:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:   
    movq    %rsp, %rbp
Ltmp1:
    movl    $42, (%rdi)
    popq    %rbp
    ret
Leh_func_end1:

这是按值返回的相同方法:

int Foo() {
  return 42;
}

变成:

__Z3Foov:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    movl    $42, %eax
    popq    %rbp
    ret
Leh_func_end1:

如您所见,您的前提是有缺陷的。两个版本产生几乎相同的机器代码。

三十年来,人们一直在为 C/C++ 生产优化编译器。您可以假设他们已经想到了任何常见的用例,并尽最大努力确保任何常见的代码模式都能生成最佳代码。永远不要改变你的编码风格来提高性能,除非你掌握了能够明确证明它存在差异的分析结果。太多此类“优化”不仅使代码更难阅读,而且实际上使性能更差。

编辑

这是与更大类型的类似比较。在这里,两种方法都返回以下内容struct

struct Bar
{
    unsigned int a;
    unsigned int b;
    unsigned int c;
    unsigned int d;
};

这段代码:

void Foo(Bar& rc) {
    rc.a = 1;
    rc.b = 2;
    rc.c = 3;
    rc.d = 4;
}

产生这个输出:

__Z3FooR3Bar:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    movl    $1, (%rdi)
    movl    $2, 4(%rdi)
    movl    $3, 8(%rdi)
    movl    $4, 12(%rdi)
    popq    %rbp
    ret

Leh_func_end1:

另一方面,这个按值返回的版本:

Bar Foo() {
    Bar rc;
    rc.a = 1;
    rc.a = 2;
    rc.a = 3;
    rc.a = 4;

    return rc;
}

最终产生更少的机器代码指令:

__Z3Foov:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    movl    $4, %eax
    xorl    %edx, %edx
    popq    %rbp
    ret
Leh_func_end1:

如果您想自己在 g++ 中进行这些比较,请使用g++ -S -O3. 其他编译器将具有类似的生成程序集的方法。

于 2013-08-25T16:30:21.487 回答
2

我认为您从错误的假设开始这个问题,即按价值返回比其他形式的回报效率低。出于几个原因,情况并非如此

  • 可以通过寄存器返回简单的内置类型,如int,等char
  • 由于命名返回值优化 ( NRVO ) ,即使是大值也可以有效地按值返回

在某些情况下,按值返回效率低下。然而,这并不意味着所有情况都是如此。

于 2013-08-25T16:15:05.453 回答
2

初学者的回答:

如果我不需要更改要传递的变量,而只希望函数的解决方案的值用于打印值之类的目的,并且我不需要将答案存储在任何地方,则按值返回是有意义的!

只是想帮忙!

于 2013-08-25T16:15:53.210 回答
1

您的假设是有争议的(已给出评论),但对于这个问题:我建议阅读

Scott Meyers 的Effective C++(第三版)
第 21 条:当必须返回对象时,不要尝试返回引用。

示例为(稍作修改):

class Rational {
public:
    Rational(int numerator = 0, int denominator = 1)
        : n(numerator), d(denominator) {}

    // ...

private:
    int n, d;

    friend Rational operator*(const Rational& lhs, const Rational& rhs);
};

使用是这样的:

    Rational a(1, 2);    // a = 1/2
    Rational b(3, 5);    // b = 3/5

    Rational c = a * b;  // c should be 3/10

和实施:

Rational operator*(const Rational& lhs, const Rational& rhs)
{
    Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
    // Would you return that local variable by reference?
    //  You don't want a dangling reference...
    //  Returning by value is the correct thing to do.
    return result;
}

或更短:

Rational operator*(const Rational& lhs, const Rational& rhs)
{
    return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

(你可以把它inline放在标题中)。

阅读完整的项目以回顾“替代方案”(动态分配、静态变量......)以及为什么它们都是坏的(这就是重点:它们都是坏的:微妙的危险甚至完全不正确) )。

最后,从那里引用:

编写必须返回新对象的函数的正确方法是让该函数返回一个新对象。

所以:当它是自然语义时,只需按值返回。

于 2013-08-25T16:57:45.157 回答
0

例如,当您想要修改返回的对象而不影响调用成员函数的对象时,您可以按值返回。在以下示例中,您可以在不更改对象(及其内部b的值)的情况下修改(由值返回):a_val

#include<iostream>
using namespace std;

class A{
public:
    A(int val) : _val(val){}
    int val(){return _val;}
private:
    int _val;
};

int main()
{
        A a(3);
        int b = a.val();
        ++b;
        cout << "a.val() = " << a.val() << endl;
        cout << "b = " << b << endl;
}

查看更多原因

于 2013-08-25T16:11:04.010 回答