在C ++中按值返回的原因是什么

时间:2013-08-25 16:07:16

标签: c++

由于按值返回大多数时候比返回引用效率低,有哪些例子按值返回是有意义的还是唯一可能的方法呢?

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

7 个答案:

答案 0 :(得分:7)

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

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

NRVO在实践中使得价值回报成为可能,因此,除非您有明确的衡量标准,表明按价值返回是一个瓶颈,不要担心。不用担心隐含的语义。

答案 1 :(得分: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)

答案 2 :(得分:3)

对于通过引用返回整数的方法使用-O3标志时,这是g ++默认生成的内容:

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。其他编译器也会有类似的组装方法。

答案 3 :(得分:2)

我认为你是以不正确的假设开始这个问题,即按价值返回的效率低于其他形式的回报。由于一些原因,情况根本不是这样

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

有些情况下,按价值返回效率低下。然而,这并不意味着每个案例都如此。

答案 4 :(得分:2)

初学者答案:

如果我不需要我要传递的变量但只是希望函数解决方案的值用于打印值而不需要将答案存储在任何地方,那么返回值是有意义的。< / p>

试着帮忙!

答案 5 :(得分: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并将其放入标题中。)

阅读完整的项目以查看“替代方案”(动态分配,静态变量......)以及详细论证为什么它们都是坏的(这就是重点:它们都是坏的< / em>:微妙危险甚至彻头彻尾不正确)。

最后,引自那里:

  

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

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

答案 6 :(得分: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;
}

请参阅more reasons