簡體   English   中英

函數實際上是如何按值返回的?

[英]How actually does a function 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<<"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);行時A b = f(a); ,它輸出:

copy-constructor, constructor, f, destructor ,假設f()中的對象y直接在目標處創建,即在對象b的內存位置創建,並且不涉及臨時值。

當我執行A c = g(a);行時A c = g(a); ,它輸出:

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

所以問題是為什么在g()cant的情況下,對象直接在c的內存位置創建,它在調用f()時發生的方式? 為什么在第二種情況下調用另一個復制構造函數(我認為是因為涉及臨時)?

問題是在第二種情況下,您將返回其中一個參數。 鑒於通常參數復制發生在調用者的站點,而不是函數內部(在本例中為main ),編譯器會生成副本,然后在進入g()再次強制復制它。

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

其次,我還沒有找到一個在返回函數參數時會忽略副本的編譯器,就像我們的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;
};

這是這個類的用法:

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;
}

這是沒有任何優化編譯時的輸出:

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()

您發布的輸出是使用命名返回值優化編譯的程序的輸出。 在這種情況下,編譯器嘗試消除冗余的復制構造函數和析構函數調用 ,這意味着在返回對象時,它將嘗試返回對象而不創建它的冗余副本。 這是啟用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()

在第一種情況下,由於NRVO已完成其工作,因此不會y in f()復制y in f()創建*tmp copy* 在第二種情況下,雖然無法應用NRVO,因為已在此函數中聲明了返回槽的另一個候選者。 有關更多信息,請參閱: C ++:使用“return”語句避免復制 :)

不同之處在於,在g情況下,您將返回傳遞給函數的值。 該標准明確說明了在12.8p31中可以省略副本的條件,並且它不包括從函數參數中刪除副本。

基本上問題是參數和返回的對象的位置是由調用約定固定的,並且編譯器不能根據實現 (在調用地點甚至可能看不到)返回的事實更改調用約定爭論。

我不久前開始了一個短暫的博客(我希望有更多的時間......)我寫了幾篇關於NRVO和復制省略的文章,可能有助於澄清這個(或者不知道,誰知道:)):

價值語義:NRVO

價值語義:復制省略

它可以(幾乎)優化整個g()函數調用,在這種情況下,您的代碼如下所示:

A a;
A c = a;

實際上這就是你的代碼正在做的事情。 現在,當您傳遞a作為按值參數(即不是引用)時,編譯器幾乎必須在那里執行復制,然后它按值返回此參數,它必須執行另一個副本。

在f()的情況下,因為它將實際上是臨時的,返回到未初始化的變量,編譯器可以看到使用c作為f()內部變量的存儲是安全的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM