Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/147.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 函数实际上是如何按值返回的?_C++_Temporary_Return By Value - Fatal编程技术网

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

C++ 函数实际上是如何按值返回的?,c++,temporary,return-by-value,C++,Temporary,Return By Value,如果我有一个类a(通过值返回对象),两个函数f()和g()的返回变量不同: class A { public: A () { cout<<"constructor, "; } A (const A& ) { cout<<"copy-constructor, "; } A& operator = (const A& ) { cout<<"assignment, "; } ~A () { cout&l

如果我有一个类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);
}
A类
{
公众:

A(){cout问题在于,在第二种情况下,您返回了一个参数。鉴于参数复制通常发生在调用方的站点,而不是在函数中(
main
,在这种情况下),编译器进行复制,然后一旦进入
g()
,就被迫再次复制

第二,我还没有找到一个编译器,可以在返回函数参数时省略副本,就像在我们的sorted实现中一样。当您思考这些省略是如何完成的时,它是有意义的:没有某种形式的过程间优化,sorted的调用方无法知道参数(而不是其他对象)将最终返回,因此编译器必须在堆栈上为参数和返回值分配单独的空间


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

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;
};
您发布的输出是使用编译的程序的输出。在这种情况下,编译器尝试消除冗余的复制构造函数和析构函数调用,这意味着在返回对象时,它将尝试返回对象而不创建其冗余副本。以下是启用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()

在第一种情况下,
*tmp通过在f()中复制
y来复制*
由于NRVO已完成其工作,因此未创建
。在第二种情况下,由于在该函数中声明了另一个返回槽候选,因此无法应用NRVO。有关详细信息,请参阅:)

不同之处在于,在
g
情况下,您返回的是传递给该函数的值。标准显式说明在何种条件下可以在12.8p31中删除副本,但不包括从函数参数中删除副本

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

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

它可以(几乎)优化整个g()函数调用,在这种情况下,代码如下所示:

A a;
A c = a;
这就是你的代码所做的。现在,当你将
作为一个by-value参数(即不是引用)传递时,编译器几乎必须在那里执行一个复制,然后它按值返回这个参数,它必须执行另一个复制


对于f(),当它将实际上是临时的内容返回到未初始化的变量中时,编译器可以看到使用
c
作为f()内部变量的存储是安全的.

如果你想让编译器执行优化,那么你必须在启用优化的情况下进行编译。我不认为这与编译器优化有任何关系,因为我已经尝试过了。是的,我知道这一点,我在代码中也做过,以了解到底发生了什么(虽然我发布了一个简化版的代码,只强调我的问题是什么)。而这个代码对我要问的问题没有任何意义。我被问到的是发生事情的原因,而不是事情本身。无论如何,感谢你的关注:)@cirronimbo:现在检查我的答案,它解释了启用NRVO的情况,也解释了我为什么向您提出这个问题。我还没有找到一个编译器,在返回函数参数时可以省略副本——这并不奇怪,不可能有一个允许这一点的调用约定和标准(在那篇文章写完后,我很高兴)明确表示编译器无法做到这一点。非常感谢。你的“[Un]defined behavior”以一种“定义良好”的方式解决了我的许多疑问:)但如果你能告诉他们,我还有一些疑问:1.在NRVO中,当你使用“bool which”从“type x and y”中选择“type”时在我的代码中,如果我将一个引用绑定到一个临时对象,即“a&b=f(a);”,那么所发生的是对象的范围(假定是f(a)返回的临时对象)增加到main的尾端大括号。这与两件事相矛盾-1.正如你在博客中提到的,获取临时地址是非法的,但我们在这里这么做。2.临时地址怎么能持续这么长时间?3.函数的本地成员对象的作用域和它的参数的作用域是不同的。就像我在类似于“A A;A b;b=g(A);”的内容,然后在“b=g(A)”行中,在赋值运算符之前调用了局部对象的析构函数,在赋值之后调用了参数的析构函数。抱歉,由于空间限制,请不要将所有三个疑问都写在一起(希望您能够处理:))1.这取决于编译器。我不希望有很多编译器会选择它,但从理论上讲这是可能的(您尝试过最高的优化级别吗?)2.标准中有一条明确的规则,在绑定常量引用时启用生命延长,并且您不使用地址,只创建引用(将引用视为别名),在这种特殊情况下,编译器可以从二进制文件中删除引用,并用原始对象的用法替换引用的用法。。。。
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()
A a;
A c = a;