#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);
}
请注意for(auto j {0}; j < i; ++j)
中的catch(...) {}
。
在此代码中,我可以将for
的条件更改为j <= i
,以避免内存泄漏。但是,如果p
在模板容器中,则之前的修订可能会导致UB。由于p + i
尚未完全构建,因此直接对其进行销毁将导致不确定的行为。
有什么办法可以避免它,或者这是类设计者的责任?
答案 0 :(得分:2)
如果这是一些面试问题,请告诉面试官您不写这样的代码,然后就可以了。
如果这是一项家庭作业,请给您的老师以下链接,以便他可以学习: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;
};
主体与您的主体相同。
答案 1 :(得分:1)
关于内存泄漏和未定义的行为:
::operator delete (p);
不会破坏您在分配的存储中手动创建的对象。
如果复制构造函数中引发了两个异常,则您将尝试多次删除同一对象。
catch块中的for循环不应泄漏内存。如果构造函数抛出该异常,则此后应该处于未初始化状态。换句话说,如果抛出new int
,将不会分配任何需要释放的空间。手动抛出异常要求您确保在构造函数抛出之前再次删除new int
分配。
您尝试在main
中编写的所有代码基本上是在重塑Foo* p = new Foo[5]{f};
的工作方式,并且即使您从构造函数(如果您使用std::unique_ptr<int>
而不是int*
。
答案 2 :(得分:1)
这与main()中发生的或未发生的无关。在构造函数中:
if(++rhs.count > 5) {
throw runtime_error("");
由于对象永远不会完成构造,因此永远不会调用其析构函数。您不能销毁某些东西,除非先对其进行构造,并且在对象完成构造之前会抛出异常。
但是,由于类的成员是使用new
构造的,并且因为没有调用析构函数,因此发生了内存泄漏。
避免内存泄漏的唯一实际方法是在抛出此异常之前手动delete x
,然后清理分配的内存;或使x
成为负责清理自身的对象(例如unique_ptr
),并在引发异常时由其析构函数来处理。在with the RAII principle行中会更多。