首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >一个函数到底是如何按值返回的?

一个函数到底是如何按值返回的?
EN

Stack Overflow用户
提问于 2012-06-06 20:54:03
回答 4查看 206关注 0票数 5

如果我有一个类A(它通过值返回一个对象),以及两个函数f()和g(),它们只是返回变量不同:

代码语言:javascript
复制
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);
}

现在,当我执行A b = f(a);行时,它输出:

copy-constructor, constructor, f, destructor,假设f()中的对象y是直接在目的地创建的,即在对象b的内存位置创建的,并且不涉及时间。

而当我执行A c = g(a);行时,它输出:

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

所以问题是,为什么在g()的情况下,对象不能直接在c的内存位置创建,就像调用f()时发生的那样?为什么它在第二种情况下调用一个额外的复制构造函数(我认为这是因为临时的参与)?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-06-06 21:55:22

不同之处在于,在g的情况下,您将返回一个传递给函数的值。该标准明确规定了在哪些条件下可以在12.8p31中省略副本,并且不包括从函数参数中省略副本。

基本上,问题是参数和返回对象的位置由调用约定固定,编译器不能基于实现(甚至可能在调用位置不可见)返回参数这一事实来更改调用约定。

我在一段时间前开始了一个短暂的博客(我希望有更多的时间...)我写了几篇关于NRVO和复制省略的文章,可能有助于澄清这一点(谁知道呢:):

Value semantics: NRVO

Value semantics: Copy elision

票数 3
EN

Stack Overflow用户

发布于 2012-06-06 21:26:36

问题是,在第二种情况下,您返回的是其中一个参数。鉴于参数复制通常发生在调用者的位置,而不是在函数内(本例中为main),编译器会进行复制,然后一旦进入g(),就会被强制再次复制。

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

第二,我还没有找到一个编译器,它可以在函数参数返回时省略副本,就像我们的

实现一样。当您考虑这些省略是如何完成的时,这是有意义的:如果没有某种形式的过程间优化,sorted的调用者无法知道参数(而不是其他对象)最终将被返回,因此编译器必须在堆栈上为参数和返回值分配单独的空间。

票数 7
EN

Stack Overflow用户

发布于 2012-06-06 21:37:47

下面是对您的代码的一小段修改,它将帮助您完全理解那里发生了什么:

代码语言:javascript
复制
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;
};

下面是这个类的用法:

代码语言:javascript
复制
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;
}

以下是在没有任何优化的情况下编译时的输出:

代码语言:javascript
复制
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()

您发布的输出是用Named Return Value Optimization编译的程序的输出。在这种情况下,编译器试图消除冗余的复制构造函数和析构函数调用,这意味着当返回对象时,它将尝试在不创建对象的冗余副本的情况下返回对象。以下是启用NRVO后的输出:

代码语言:javascript
复制
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()

在第一种情况下,由于NRVO已经完成了它的工作,所以不会通过复制y in f()来创建*tmp copy*。然而,在第二种情况下,不能应用NRVO,因为在此函数中声明了返回槽的另一个候选者。有关更多信息,请参阅:C++ : Avoiding copy with the "return" statement :)

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/10914633

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档