2014-08-30 62 views
0

我讀了一些關於RVO的C++,並發現了一個奇怪的觀察。我跑到下面的代碼..問題與返回值優化

class myClass { 
    private: 
    int *ptr; 
    static int id; 

    public: 
    myClass() { 
     id++; 
     ptr = new int[10]; 
     printf("Created %p id %d this %p\n", ptr, id, this); 
    } 

    ~myClass() { 
     delete[] ptr; 
     ptr = NULL; 
     printf("Deleted ptr id %d this %p\n", this->id, this); 
     id--; 
    } 
}; 

int myClass::id = 0; 

myClass testFunc(myClass var) { 
    myClass temp; 
    return temp; 
} 

int main() { 
    myClass var1, var2; 

    testFunc(var1); 

    return 0; 
} 

我得到的O/P爲

Created 0x9b14008 id 1 this 0xbfe3e910 
Created 0x9b14038 id 2 this 0xbfe3e914 
Created 0x9b14068 id 3 this 0xbfe3e91c 
Deleted ptr id 3 this 0xbfe3e91c 
Deleted ptr id 2 this 0xbfe3e918 
Deleted ptr id 1 this 0xbfe3e914 
Deleted ptr id 0 this 0xbfe3e910 

在調用testFunc臨時副本變量實際上會導致一些問題。它刪除了var1的ptr成員,這可以通過調用指針0xbfe3e918中的析構函數來看到。在valgring這個代碼顯示沒有內存泄漏,但無效刪除[]。

我有點困惑如何額外的析構函數被調用,爲什麼沒有相應的構造函數調用相同?

+1

規則3(5)...複製構造函數... – Jarod42 2014-08-30 16:12:31

+0

可能重複[什麼是三條規則?](http://stackoverflow.com/questions/4172722/what-is-the-rule -of-three) – 2014-08-30 16:16:39

回答

1

這實際上與返回值優化沒有任何關係。只有在使用testFunc的結果時,RVO纔會顯着。

你所看到的問題是,因爲你的類是隻使用默認的拷貝構造函數,所以當var傳遞給testFuncptr成員被複制,就像一個普通的指針,而無需創建它是對象的新副本指向。

正因爲如此,你最終這都指向同一個底層int陣列中兩個myClass對象,這兩個對象的析構函數被調用時,他們試圖刪除同一int陣列的兩倍。

0

按值傳遞var1作爲函數參數創建副本。這是用(隱式定義的)複製構造函數完成的,這就是爲什麼你的程序不打印任何東西 - 你只能在默認構造函數中打印某些東西。

您現在有一個大問題:對象的兩個副本都包含一個指向同一數組的指針,並且兩者都會在銷燬時嘗試將其刪除。這種雙重刪除是一個錯誤,導致未定義的行爲。

要解決此問題,請按照Rule of Three的說明使該類可以正確複製(或不可複製);或者停止使用原始內存的所有這些危險的操作,並使用std::vector或類似命令爲您正確管理陣列。

0

當調用testFunc時,var被初始化爲來自main的var1的副本。這是通過使用默認的複製構造函數完成的,因此「缺少」構造函數調用。這只是另一個構造函數被調用。

所以問題是,現在varvar1的副本,這意味着var.ptr必須具有與var1.ptr相同的值。

1.解決方案是提供自己的複製構造函數來處理這種情況。一種解決辦法是製作深層複製:

myClass(const myClass& o) { 
    id++; 
    ptr = new int[10]; 
    memcpy(o.ptr, ptr, 10); 
    printf("Created copy %p of %p id %d this %p\n", ptr, o.ptr, id, this); 
} 

2。另一種解決方案是使用一個shared_ptr,可以保持具有特定指針的副本對象的數量的軌道:

//int *ptr;   //Replace this 
shared_ptr<int> ptr; //With this 

//Initialize ptr: 
myClass():ptr(new int[10]) { 
    //... 
} 

// Also eliminate cleenup. It's handled by shared_ptr: 
~myClass() { 
    printf("Deleted ptr id %d this %p\n", this->id, this); 
    id--; 
} 

shared_ptr方法的問題是,他們仍然共享相同的指針。這意味着它不會像通常所期望的那樣表現出副本。如果ptr指向的值永遠不會更改,那麼可以確定。

我寧願解決方案。

+0

在下面的代碼中,我使用了自己的拷貝構造函數。但我仍然可以看到額外的析構函數。如果我取消註釋func()中的返回行,那麼它工作正常。我有點困惑與RVO的工作方式.. – 2014-09-18 16:18:13