简体   繁体   English

函数实际上是如何按值返回的?

[英]How actually does a function return by value?

If I have a class A (which returns an object by value ), and two functions f() and g() having difference in just their return variables : 如果我有一个类A(按值返回一个对象),并且两个函数f()和g()只有它们的返回变量不同:

class A
{
    public:
    A () { cout<<"constructor, "; }
    A (const A& ) { cout<<"copy-constructor, "; }
    A& operator = (const A& ) { cout<<"assignment, "; }
    ~A () { cout<<"destructor, "; }
};
    const A f(A x)
    {A y; cout<<"f, "; return y;}

    const A g(A x)
    {A y; cout<<"g, "; return x;}

main()
{
    A a;
    A b = f(a);
    A c = g(a);
}

Now when I execute the line A b = f(a); 现在当我执行A b = f(a);行时A b = f(a); , it outputs: ,它输出:

copy-constructor, constructor, f, destructor , which is fine assuming that object y in f() is created directly at the destination ie at the memory location of object b, and no temporaries involved. copy-constructor, constructor, f, destructor ,假设f()中的对象y直接在目标处创建,即在对象b的内存位置创建,并且不涉及临时值。

While when I execute the line A c = g(a); 当我执行A c = g(a);行时A c = g(a); , it outputs: ,它输出:

copy-constructor, constructor, g, copy-constructor, destructor, destructor, . copy-constructor, constructor, g, copy-constructor, destructor, destructor, .

So the question is why in the case of g() cant the object be directly created at memory location of c, the way it happened while calling f() ? 所以问题是为什么在g()cant的情况下,对象直接在c的内存位置创建,它在调用f()时发生的方式? Why it calls an additional copy-constructor ( which I presume is because of the involvement of temporary ) in the 2nd case ? 为什么在第二种情况下调用另一个复制构造函数(我认为是因为涉及临时)?

The problem is that in the second case, you're returning one of the parameters. 问题是在第二种情况下,您将返回其中一个参数。 Given that usually parameter copying occurs at the site of the caller, not within the function ( main in this case), the compiler makes the copy, and then is forced to copy it again once it enters g() . 鉴于通常参数复制发生在调用者的站点,而不是函数内部(在本例中为main ),编译器会生成副本,然后在进入g()再次强制复制它。

From http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ 来自http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

Second, I've yet to find a compiler that will elide the copy when a function parameter is returned, as in our implementation of sorted. 其次,我还没有找到一个在返回函数参数时会忽略副本的编译器,就像我们的sorted实现一样。 When you think about how these elisions are done, it makes sense: without some form of inter-procedural optimization, the caller of sorted can't know that the argument (and not some other object) will eventually be returned, so the compiler must allocate separate space on the stack for the argument and the return value. 当你考虑如何完成这些约定时,它是有道理的:没有某种形式的过程间优化,被排序的调用者不能知道最终将返回参数(而不是其他一些对象),因此编译器必须在堆栈上为参数和返回值分配单独的空间。

Here's a little modification of your code, that will help you to perfectly understand what's going on there: 这是对您的代码的一点修改,这将帮助您完全理解那里发生的事情:

class A{
public:
    A(const char* cname) : name(cname){
        std::cout << "constructing " << cname << std::endl;
    }
    ~A(){
        std::cout << "destructing " << name.c_str() << std::endl;
    }
    A(A const& a){
        if (name.empty()) name = "*tmp copy*";
        std::cout 
            << "creating " << name.c_str() 
            << " by copying " << a.name.c_str() << std::endl;
    }
    A& operator=(A const& a){
        std::cout
            << "assignment ( "
                << name.c_str() << " = " << a.name.c_str()
            << " )"<< std::endl;
        return *this;
    }
    std::string name;
};

Here's the usage of this class: 这是这个类的用法:

const A f(A x){
    std::cout 
        << "// renaming " << x.name.c_str() 
        << " to x in f()" << std::endl;
    x.name = "x in f()";
    A y("y in f()");
    return y;
}

const A g(A x){
    std::cout 
        << "// renaming " << x.name.c_str()
        << " to x in f()" << std::endl;
    x.name = "x in g()";
    A y("y in g()");
    return x;
}

int main(){
    A a("a in main()");
    std::cout << "- - - - - - calling f:" << std::endl;
    A b = f(a);
    b.name = "b in main()";
    std::cout << "- - - - - - calling g:" << std::endl;
    A c = g(a);
    c.name = "c in main()";
    std::cout << ">>> leaving the scope:" << std::endl;
    return 0;
}

and here's the output when compiled without any optimization: 这是没有任何优化编译时的输出:

constructing a in main()
- - - - - - calling f:
creating *tmp copy* by copying a in main()
// renaming *tmp copy* to x in f()
constructing y in f()
creating *tmp copy* by copying y in f()
destructing y in f()
destructing x in f()
- - - - - - calling g:
creating *tmp copy* by copying a in main()
// renaming *tmp copy* to x in f()
constructing y in g()
creating *tmp copy* by copying x in g()
destructing y in g()
destructing x in g()
>>> leaving the scope:
destructing c in main()
destructing b in main()
destructing a in main()

The output you posted is the output of program compiled with Named Return Value Optimization . 您发布的输出是使用命名返回值优化编译的程序的输出。 In this case the compiler tries to eliminate redundant Copy constructor and Destructor calls which means that when returning the object, it will try to return the object without creating redundant copy of it. 在这种情况下,编译器尝试消除冗余的复制构造函数和析构函数调用 ,这意味着在返回对象时,它将尝试返回对象而不创建它的冗余副本。 Here's the output with NRVO enabled: 这是启用NRVO的输出:

constructing a in main()
- - - - - - calling f:
creating *tmp copy* by copying a in main()
// renaming *tmp copy* to x in f()
constructing y in f()
destructing x in f()
- - - - - - calling g:
creating *tmp copy* by copying a in main()
// renaming *tmp copy* to x in f()
constructing y in g()
creating *tmp copy* by copying x in g()
destructing y in g()
destructing x in g()
>>> leaving the scope:
destructing c in main()
destructing b in main()
destructing a in main()

In first case, *tmp copy* by copying y in f() is not created since NRVO has done its job. 在第一种情况下,由于NRVO已完成其工作,因此不会y in f()复制y in f()创建*tmp copy* In second case though NRVO can't be applied because another candidate for return slot has been declared within this function. 在第二种情况下,虽然无法应用NRVO,因为已在此函数中声明了返回槽的另一个候选者。 For more information see: C++ : Avoiding copy with the "return" statement :) 有关更多信息,请参阅: C ++:使用“return”语句避免复制 :)

The difference is that in the g case, you are returning a value that was passed to the function. 不同之处在于,在g情况下,您将返回传递给函数的值。 The standard explicitly states under which conditions the copy can be elided in 12.8p31 and it does not include eliding the copy from a function argument. 该标准明确说明了在12.8p31中可以省略副本的条件,并且它不包括从函数参数中删除副本。

Basically the problem is that the location of the argument and the returned object are fixed by the calling convention, and the compiler cannot change the calling convention based on the fact that the implementation (that might not even be visible at the place of call) returns the argument. 基本上问题是参数和返回的对象的位置是由调用约定固定的,并且编译器不能根据实现 (在调用地点甚至可能看不到)返回的事实更改调用约定争论。

I started a short lived blog some time ago (I expected to have more time...) and I wrote a couple of articles about NRVO and copy elision that might help clarify this (or not, who knows :)): 我不久前开始了一个短暂的博客(我希望有更多的时间......)我写了几篇关于NRVO和复制省略的文章,可能有助于澄清这个(或者不知道,谁知道:)):

Value semantics: NRVO 价值语义:NRVO

Value semantics: Copy elision 价值语义:复制省略

it can (almost) optimise the entire g() function call away, in which case your code looks like this: 它可以(几乎)优化整个g()函数调用,在这种情况下,您的代码如下所示:

A a;
A c = a;

as effectively this is what your code is doing. 实际上这就是你的代码正在做的事情。 Now, as you pass a as a by-value parameter (ie not a reference) then the compiler almost has to perform a copy there, and then it returns this parameter by value, it has to perform another copy. 现在,当您传递a作为按值参数(即不是引用)时,编译器几乎必须在那里执行复制,然后它按值返回此参数,它必须执行另一个副本。

In the case of f(), as it it returning what is effectively a temporary, into a uninitialised variable, the compiler can see that it is safe to use c as the storage for the internal variable inside f(). 在f()的情况下,因为它将实际上是临时的,返回到未初始化的变量,编译器可以看到使用c作为f()内部变量的存储是安全的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM