这C++代码泄漏内存吗?
Does this C++ code leak memory?
struct Foo
{
Foo(int i)
{
ptr = new int(i);
}
~Foo()
{
delete ptr;
}
int* ptr;
};
int main()
{
{
Foo a(8);
Foo b(7);
a = b;
}
//Do other stuff
}
如果我理解正确,编译器将自动为Foo
创建一个赋值运算符成员函数。但是,这只是将ptr
的值放在b
中并将其放入a
。a
分配的内存最初似乎丢失了。我可以在进行分配之前a.~Foo();
调用,但我在某处听说您很少需要显式调用析构函数。因此,假设我为 Foo
编写了一个赋值运算符,该运算符在将 r 值分配给 l 值之前删除左操作数的int
指针。这样:
Foo& operator=(const Foo& other)
{
//To handle self-assignment:
if (this != &other) {
delete this->ptr;
this->ptr = other.ptr;
}
return *this;
}
但是如果我这样做,那么当Foo a
和Foo b
超出范围时,它们的析构函数是否都运行,删除同一个指针两次(因为它们现在都指向同一件事)?
编辑:
如果我正确理解Anders K,这是正确的方法:
Foo& operator=(const Foo& other)
{
//To handle self-assignment:
if (this != &other) {
delete this->ptr;
//Clones the int
this->ptr = new int(*other.ptr);
}
return *this;
}
现在,a
克隆了b
指向的int
,并设置了自己的指针。也许在这种情况下,delete
和new
不是必需的,因为它只涉及int
,但如果数据成员不是int*
而是Bar*
或其他什么,则可能需要重新分配。
编辑 2:最好的解决方案似乎是复制和交换成语。
这是否会泄漏内存?不,它没有。
似乎大多数人都错过了这里的重点。所以这里有一点澄清。
此答案中"不,它不会泄漏"的最初响应是不正确的,但此处建议的解决方案是解决问题的唯一和最合适的解决方案。
解决你的困境的办法是:
不使用指向整数成员(int *
)的指针,而是只使用整数(int
),这里实际上并不需要动态分配的指针成员。您可以使用int
作为成员来实现相同的功能。
请注意,在C++中,您应该尽可能少地使用new
。
如果由于某种原因(我在代码示例中看不到)你不能没有动态分配的指针成员继续阅读:
你需要遵循三法则!
为什么你需要遵循三法则?
三法则指出:
如果您的班级需要
复制构造函数,
赋值运算符,
或析构函数,那么很可能需要这三个。
您的类需要自己的显式析构函数,因此它还需要一个显式复制构造函数和复制赋值运算符。
由于类的复制构造函数和复制赋值运算符是隐式的,因此它们也是隐式公共的,这意味着类设计允许复制或赋值此类的对象。这些函数的隐式生成版本只会创建动态分配的指针成员的浅拷贝,这会将您的类公开给:
- Memory Leaks &
- 悬空指针和
- 双重释放的潜在未定义行为
这基本上意味着你不能使用隐式生成的版本,你需要提供你自己的重载版本,这就是三规则一开始所说的。
显式提供的重载应该创建已分配成员的深层副本,从而防止所有问题。
如何正确实现复制赋值运算符?
在这种情况下,提供复制赋值运算符的最有效和优化的方法是使用:
复制和交换成语
@GManNickG著名的答案提供了足够的细节来解释它提供的优势。
建议:
此外,最好使用智能指针作为类成员,而不是原始指针,这会增加显式内存管理的负担。智能指针将隐式为您管理内存。使用哪种智能指针取决于适用于成员的生存期和所有权语义,您需要根据需要选择合适的智能指针。
处理此问题的正常方法是创建指针指向的对象的克隆,这就是为什么使用赋值运算符很重要的原因。 当没有定义 assigment 运算符时,默认行为是 memcpy,当两个析构函数尝试删除同一对象时,这将导致崩溃,并且由于 ptr 在 B 中指向的先前值以来的内存泄漏将不会被删除。
Foo a
+-----+
a->ptr-> | |
+-----+
Foo b
+-----+
b->ptr-> | |
+-----+
a = b
+-----+
| |
+-----+
a->ptr
+-----+
b->ptr | |
+-----+
when a and b go out of scope delete will be called twice on the same object.
编辑:正如本杰明/阿尔斯正确指出的那样,上面只是指这个特定的例子,见下面的评论
所示代码具有未定义的行为。因此,如果它泄漏内存(如预期的那样),那么这只是 UB 的一种可能表现形式。它还可以向巴拉克·奥巴马发送愤怒的威胁信,或者喷出红色(或橙色)鼻恶魔,或者什么都不做,或者表现得好像没有记忆泄漏,奇迹般地恢复了记忆,或者其他什么。
解决方案:代替int*
,使用int
,即
struct Foo
{
Foo(int i): blah( i ) {}
int blah;
};
int main()
{
{
Foo a(8);
Foo b(7);
a = b;
}
//Do other stuff
}
这更安全、更短、更高效、更清晰。
对于这个问题,没有其他解决方案在任何客观衡量标准上都胜过上述解决方案。
- 从构造函数抛出异常时如何克服内存泄漏
- malloc() 可能出现内存泄漏
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 尽管遵循了规则,内存泄漏在哪里
- 为什么调用堆栈数组会导致内存泄漏
- 在简单示例中,Python3 + ctypes 回调会导致内存泄漏
- 使用模板类的自定义列表类型中的内存泄漏
- 为什么以下C++代码中存在内存泄漏?
- OpenCV 我应该使用智能指针来防止内存泄漏吗?
- 我是否生成线程并导致内存泄漏?
- 多线程程序中出现意外的内存泄漏
- 为什么此函数会导致内存泄漏?
- 在 C++ 库中使用cythonized python时内存泄漏
- 需要帮助查找内存泄漏
- 瓦尔格林德的内存泄漏使用新的
- 无法找出我的代码中的内存泄漏
- C++ 结构内部的unordered_map会导致内存泄漏问题吗?
- 可视化 使用 VS Code 查找C++应用程序中的内存泄漏
- Shared_ptr双链接列表内存泄漏
- C++ 在类中使用常量引用文本时 O2 内存泄漏