有没有办法避免以下代码中的内存泄漏?
Is there any way to avoid memory leak in the following code?
#include <new>
class Foo {
public:
int *x;
mutable size_t count {1};
Foo() : x {new int} {}
Foo(const Foo &rhs) : x {new int} {
if(++rhs.count > 5) {
throw runtime_error("");
}
}
~Foo() {
delete x;
}
Foo &operator=(const Foo &) = delete;
};
int main(int argc, char *argv[]) {
Foo *p {reinterpret_cast<Foo *>(::operator new (sizeof(Foo) * 5))};
Foo f;
for(auto i {0}; i < 5; ++i) {
try {
new (p + i) Foo(f);
}catch(...) {
for(auto j {0}; j < i; ++j) { //!
(p + j)->~Foo();
}
}
}
::operator delete (p);
}
请关注catch(...) {}
中的for(auto j {0}; j < i; ++j)
。
在这段代码中,我可以将 for
的条件更改为 j <= i
以避免内存泄漏。但是如果p
在模板容器中,之前的修改可能会导致UB。因为p + i
还没有构造完整,直接销毁它会导致未定义的行为。
有什么办法可以避免,还是class设计师的责任?
如果这是一些面试问题,请告诉面试官你不会写那样的代码,然后你就会得到这份工作。
如果这是一些家庭作业,请给你的老师以下内容 link 这样他就可以学到一些东西:https://www.aristeia.com/EMC++.html
最后,回答你的问题:
int main(int argc, char *argv[]) {
std::unique_ptr<Foo> p[5];
Foo f;
try {
for (int i=0;i<5;++i) {
//p[i]=std::make_unique<Foo>(f); //Only for C++14
p[i]=std::unique_ptr<Foo>(new Foo(f));
}
} catch (...) {
//Nothing, all is done "magically" by unique_ptr
}
}
现在,实际上回答您的问题并使您的代码更加人为,您可以使用构造函数初始化列表 try-catch(更多 here)
class Foo {
public:
int *x;
mutable size_t count {1};
Foo() : x {new int} {}
Foo(const Foo &rhs) try: x {new int} {
if(++rhs.count > 5) {
throw runtime_error("");
}
} catch (...) {
delete x;
throw;
}
~Foo() {
delete x;
}
Foo &operator=(const Foo &) = delete;
};
主要和你一样
至于内存泄漏和未定义的行为:
::operator delete (p);
不会破坏您在分配的存储中手动创建的对象。
如果拷贝构造函数抛出两个异常,你会多次尝试删除同一个对象
catch 块中的 for 循环不应泄漏内存。如果构造函数抛出它之后应该处于未初始化状态。换句话说,如果 new int
抛出,则不会分配需要释放的 space。您的手动异常抛出要求您确保在构造函数抛出之前再次删除 new int
分配。
您尝试在 main
中编写的所有代码基本上都是在重新发明 Foo* p = new Foo[5]{f};
会做的事情,并且 class 中的内存管理会起作用如果您使用 std::unique_ptr<int>
而不是 int*
.
,即使您从构造函数中抛出也会自动
这与main() 中发生或未发生的任何事情无关。在构造函数中:
if(++rhs.count > 5) {
throw runtime_error("");
因为对象永远不会完成构造,所以它的析构函数永远不会被调用。除非先构造了某物,否则无法销毁它,并且在对象完成构造之前抛出异常。
但是因为 class 的一个成员是用 new
构造的,并且因为没有调用析构函数,所以这就是内存泄漏发生的地方。
避免内存泄漏的唯一实用方法是在抛出此异常之前手动 delete x
并清理您分配的内容;或使 x
成为负责清理自身的对象,如 unique_ptr
,当抛出异常时,其析构函数将处理它。这将更符合 with the RAII principle.
#include <new>
class Foo {
public:
int *x;
mutable size_t count {1};
Foo() : x {new int} {}
Foo(const Foo &rhs) : x {new int} {
if(++rhs.count > 5) {
throw runtime_error("");
}
}
~Foo() {
delete x;
}
Foo &operator=(const Foo &) = delete;
};
int main(int argc, char *argv[]) {
Foo *p {reinterpret_cast<Foo *>(::operator new (sizeof(Foo) * 5))};
Foo f;
for(auto i {0}; i < 5; ++i) {
try {
new (p + i) Foo(f);
}catch(...) {
for(auto j {0}; j < i; ++j) { //!
(p + j)->~Foo();
}
}
}
::operator delete (p);
}
请关注catch(...) {}
中的for(auto j {0}; j < i; ++j)
。
在这段代码中,我可以将 for
的条件更改为 j <= i
以避免内存泄漏。但是如果p
在模板容器中,之前的修改可能会导致UB。因为p + i
还没有构造完整,直接销毁它会导致未定义的行为。
有什么办法可以避免,还是class设计师的责任?
如果这是一些面试问题,请告诉面试官你不会写那样的代码,然后你就会得到这份工作。
如果这是一些家庭作业,请给你的老师以下内容 link 这样他就可以学到一些东西:https://www.aristeia.com/EMC++.html
最后,回答你的问题:
int main(int argc, char *argv[]) {
std::unique_ptr<Foo> p[5];
Foo f;
try {
for (int i=0;i<5;++i) {
//p[i]=std::make_unique<Foo>(f); //Only for C++14
p[i]=std::unique_ptr<Foo>(new Foo(f));
}
} catch (...) {
//Nothing, all is done "magically" by unique_ptr
}
}
现在,实际上回答您的问题并使您的代码更加人为,您可以使用构造函数初始化列表 try-catch(更多 here)
class Foo {
public:
int *x;
mutable size_t count {1};
Foo() : x {new int} {}
Foo(const Foo &rhs) try: x {new int} {
if(++rhs.count > 5) {
throw runtime_error("");
}
} catch (...) {
delete x;
throw;
}
~Foo() {
delete x;
}
Foo &operator=(const Foo &) = delete;
};
主要和你一样
至于内存泄漏和未定义的行为:
::operator delete (p);
不会破坏您在分配的存储中手动创建的对象。如果拷贝构造函数抛出两个异常,你会多次尝试删除同一个对象
catch 块中的 for 循环不应泄漏内存。如果构造函数抛出它之后应该处于未初始化状态。换句话说,如果
new int
抛出,则不会分配需要释放的 space。您的手动异常抛出要求您确保在构造函数抛出之前再次删除new int
分配。您尝试在
main
中编写的所有代码基本上都是在重新发明Foo* p = new Foo[5]{f};
会做的事情,并且 class 中的内存管理会起作用如果您使用std::unique_ptr<int>
而不是int*
. ,即使您从构造函数中抛出也会自动
这与main() 中发生或未发生的任何事情无关。在构造函数中:
if(++rhs.count > 5) {
throw runtime_error("");
因为对象永远不会完成构造,所以它的析构函数永远不会被调用。除非先构造了某物,否则无法销毁它,并且在对象完成构造之前抛出异常。
但是因为 class 的一个成员是用 new
构造的,并且因为没有调用析构函数,所以这就是内存泄漏发生的地方。
避免内存泄漏的唯一实用方法是在抛出此异常之前手动 delete x
并清理您分配的内容;或使 x
成为负责清理自身的对象,如 unique_ptr
,当抛出异常时,其析构函数将处理它。这将更符合 with the RAII principle.