簡體   English   中英

為什么內存泄漏僅在賦值運算符重載但不在復制構造函數中以及復制和交換習慣用法如何解析時發生

[英]Why memory leak only happens in case of assignment operator overloading but not in copy constructor and how copy and swap idiom resolves it

PS:我是編程新手,所以請用簡單的語言回答我的疑惑。 我找到了幾個答案,但無法理解它們。 下面是復制構造函數和賦值運算符重載。

template <class T>
Mystack<T>::Mystack(const Mystack<T> &source)            // copy constructor
{
    input = new T[source.capacity];
    top = source.top;
    capacity = source.capacity;
    for (int i = 0; i <= source.top; i++)
    {
        input[i] = source.input[i];
    }
}


template <class T>
Mystack<T> & Mystack<T>::operator=(const Mystack<T> &source)       // assignment operator overload 
{
    input = new T[source.capacity];
    top = source.top;
    capacity = source.capacity;

    for (int i = 0; i <= source.top; i++)
    {
        input[i] = source.input[i];
    }
    return *this;
}

主要功能片段

   Mystack <int> intstack = tempstack; (copy constructor)
    Mystack <int> floatstack, temp_1;
    floatstack = temp_1;  (assignment operator)

理解:我理解我們需要復制和賦值運算符,以便我們可以進行深度復制,以防我們使用堆內存,因此當我們刪除其中一個對象時不會有懸空指針問題。

有人可以回答下面的問題。

1 .:我的理解是否正確?

2 . :開發人員建議我在賦值運算符中有內存泄漏。 如果是,可以請一些人解釋我怎么樣?

3.復制構造函數與賦值運算符具有或多或少相同的代碼,那么為什么我只有在賦值運算符的情況下才有內存泄漏但在復制構造函數中卻沒有。

4 . :如果我真的有內存泄漏。 什么魔術副本和交換習慣用於解決內存泄漏問題。

PS:它不是完整的運行代碼。 在實際代碼中,對象確實包含一些數據。 請耐心!

“我的理解是否正確?”

是的你似乎明白了。 完整的理由最好由三規則概念描述。 如果你發現自己必須實現三個中的任何一個(copy-ctor,賦值操作或析構函數)來管理動態內存,你可能需要全部三個 (也許更多,請參閱文章)。


“開發人員已經建議我在賦值運算符中有內存泄漏。如果是,有些人可以解釋一下我是怎么回事?”

你沒有發布你的默認構造函數,但我認為它看起來像這樣:

Mystack<T>::Mystack(size_t size = N)
{
    input = new T[size];
    top = 0;
    capacity = size;
}

或類似的東西。 現在,讓我們看看你的賦值運算符會發生什么:

input = new T[source.capacity]; 

嗯,這個對象input值剛剛發生了什么? 它不再可達,隨之而來的是其中的記憶不再可回收。 它被泄露了。


“復制構造函數與賦值運算符具有或多或少相同的代碼,那么只有在賦值運算符的情況下才會出現內存泄漏,但在復制構造函數中卻沒有。”

在copy-ctor中,復制結構的目標中沒有分配input 先前值。 input還沒有指向任何東西(怎么可能?你只是 - 現在正在創建目標對象)。 因此,沒有泄漏。


“如果我真的有內存泄漏。那個內存泄漏得到解決的魔術副本和交換習慣用法。”

copy-swap慣用法使用copy-constructor創建一個臨時保存的值副本 ,然后使用賦值運算符將對象“guts”與該副本交換。 在執行此操作時,當析構函數觸發時,外出臨時將破壞目標對象的原始內容,而目標對象將臨時傳入內容的所有權作為其自身。

這提供了多種好處(是的,一個缺點), 並且在這里有出色的描述 代碼中的一個簡單示例是:

template <class T>
void Mystack<T>::swap(Mystack<T>& src)
{
    std::swap(input, src.input);
    std::swap(top, src.top);
    std::swap(capacity, src.capacity);
}

並且您的賦值運算符變為:

template <class T>
Mystack<T> & Mystack<T>::operator=(Mystack<T> src) // NOTE by-value intentional,
                                                   // invokes copy-ctor.
{
    this->swap(src);
    return *this;
}

現在你有一個復制(在copy-ctor中)進行管理的實現。 此外,如果發生任何異常,他們將在構建價值副本期間這樣做,而不是在此處。 這個物體受到污染的可能性不確定狀態會減少(一件好事)

如果你對我之前提到的缺點感到好奇,那么考慮一下自我賦值( x = x; )如何與這樣的范式一起發揮作用。 老實說,我個人並不認為自我指派無效率是一個缺點。 如果您的代碼經常使用x = x; 你的設計中可能有一股腐臭味。


強烈建議您閱讀有關該概念的其他信息的文章 這是一個可能改變的方式 - 你如何思考你將會記住你職業生涯的其他事情。

復制構造函數的目的是使類的兩個實例的input指針不會最終指向堆中的同一緩沖區。 如果他們這樣做,修改一個堆棧將影響另一個堆棧,一個堆棧的析構函數將釋放另一個堆棧的內存,從而導致釋放后使用錯誤。

您的賦值運算符確實會導致內存泄漏,因為它不會釋放先前分配給該堆棧實例的內存。 因此,當調用析構函數時, input指向的緩沖區不會最終被解除分配。 這對復制構造函數來說不是問題,因為它只在類的新實例上調用,該實例之前沒有分配任何內存。 要解決此問題,請將以下行添加到賦值運算符的開頭:

delete [] input;

暫無
暫無

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

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