繁体   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