为什么内存泄漏只发生在赋值运算符重载的情况下,而不是在复制构造函数中,以及复制和交换习惯用法如何解决它
Why memory leak only happens in case of assignment operator overloading but not in copy constructor and how copy and swap idiom resolves it
P.S:我是编程新手,所以请用更简单的术语回答我的疑问。我找到了几个答案,但无法理解。下面是复制构造函数和赋值运算符重载。
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、assignment op 或析构函数)来管理动态内存,那么您很可能需要这三者(也许更多,请参阅文章)。
"开发人员建议我在赋值运算符中存在内存泄漏。如果是,有人可以解释一下我怎么做吗?
您尚未发布默认构造函数,但我认为它看起来像这样:
Mystack<T>::Mystack(size_t size = N)
{
input = new T[size];
top = 0;
capacity = size;
}
或类似的东西。现在,让我们看看赋值运算符中会发生什么:
input = new T[source.capacity];
嗯,这个对象的旧值刚刚发生了什么input
?它不再可访问,并且其中的内存不再可回收。它被泄露了。
"复制构造函数或在多或少与赋值运算符具有相同的代码,那么为什么我只在赋值运算符的情况下才有内存泄漏,而在复制构造函数中没有。"
复制 ctor 的复制构造目标中没有先前分配的input
值。 即 input
还没有指向任何东西(怎么可能?你现在正在创建目标对象)。因此,没有泄漏。
"万一我真的有记忆泄漏。什么神奇的复制和交换成语解决了内存泄漏。
复制交换习惯用法使用 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;
}
现在,您有一个复制实现(在复制ctor中)要管理。此外,如果发生任何异常,他们将在构建价值副本期间这样做,而不是在这里。该物体被污染到不确定状态的机会减少(一件好事)
如果你对我之前提到的缺点感到好奇,请考虑一下自我分配(x = x;
)在这样的范式中会如何发挥作用。老实说,我个人并不认为自我分配效率低下是一个缺点。如果你的代码经常出现在x = x;
这样的代码中,那么你的设计一开始就有一种腐烂的气味。
我强烈建议您阅读本文以获取有关该概念的其他信息。这是那些可能会改变你的想法的事情之一,你会在你的职业生涯中记住。
复制构造函数的目的是使类的两个实例的input
指针最终不会指向堆中的同一缓冲区。 如果这样做,修改一个堆栈将影响另一个堆栈,其中一个堆栈的析构函数将释放另一个堆栈的内存,从而导致释放后使用错误。
赋值运算符确实会导致内存泄漏,因为它不会释放之前分配给堆栈实例的内存。 因此,input
指向的缓冲区在调用析构函数时最终不会被取消分配。 这不是复制构造函数的问题,因为它只在类的新实例上调用,该实例没有从以前分配给它的任何内存。 为了解决此问题,请将以下行添加到赋值运算符的开头:
delete [] input;
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 在C++程序中输入的文本文件将不起作用,除非文本被复制和粘贴
- 使用strcpy将char数组的元素复制到另一个数组
- 是否可以初始化不可复制类型的成员变量(或基类)
- 复制和交换习惯用法与移动操作之间的交互
- 复制交换习惯用法-我们可以在这里使用动态强制转换操作吗
- 错误:使用复制和交换习惯用法的交换函数中"operator="的重载不明确
- C++移动分配可防止复制交换习惯用法
- 当涉及分配器时,是否有类似于复制和交换习惯用法的东西
- 是具有复制和交换习惯用法的复制赋值运算符,建议进行自赋值检查
- 使用复制和交换习惯用法,复制对象的析构函数如何不解除分配指向内存
- 在复制和交换习惯用法中实现交换
- 为什么内存泄漏只发生在赋值运算符重载的情况下,而不是在复制构造函数中,以及复制和交换习惯用法如何解决它
- C++:复制和交换习惯用法,替代构造函数
- 如何最好地处理具有未初始化内存的复制交换习惯用法
- c++ 11的移动语义优于写时复制的习惯用法
- 如何实现拷贝交换习惯用法的复制构造函数
- 移动构造函数和赋值操作符,使用复制-交换习惯实现
- 重用复制-交换习惯用法
- 为什么SGI STL不使用复制和交换习惯用语?