如果构造函数抛出异常,如何删除对象?
How to delete object if constructor throws an exception?
所以我们有一个构造函数可以根据传递给它的参数抛出异常,但我们不知道如果发生这种情况如何删除对象。代码重要部分:
try
{
GameBase *gameptr = GameBase::getGame(argc, argv);
if (gameptr == 0)
{
std::cout << "Correct usage: " << argv[PROGRAM_NAME] << " " << "TicTacToe" << std::endl;
return NO_GAME;
}
else
{
gameptr->play();
}
delete gameptr;
}
catch (error e)
{
if (e == INVALID_DIMENSION)
{
std::cout << "Win condition is larger than the length of the board." << std::endl;
return e;
}
}
catch (...)
{
std::cout << "An exception was caught (probably bad_alloc from new operator)" << std::endl;
return GENERIC_ERROR;
}
在第三行,GameBase::getGame()
调用派生自 GameBase
的其中一个游戏的构造函数和 returns 指向该游戏的指针,这些构造函数可以抛出异常。问题是,如果发生这种情况,我们如何才能删除 gameptr
指向的(部分?)对象?如果抛出异常,我们将退出 gameptr
的范围,因为我们离开了 try 块,无法调用 delete gameptr
.
要评估异常安全性,您需要在 GameBase::getGame
中提供对象构造的更多详细信息。
规则是这样的,如果构造函数抛出异常,则不会创建对象,因此不会调用析构函数。关联的内存分配也被释放(即对象本身的内存)。
那么问题就变成了,一开始内存是怎么分配的?如果它带有 new GameBase(...)
,则无需释放或删除结果指针 - 内存由运行时释放。
为了清楚地说明已经构造的成员变量发生了什么;它们在 "parent" 对象异常时被破坏。考虑 sample code;
#include <iostream>
using namespace std;
struct M {
M() { cout << "M ctor" << endl; }
~M() { cout << "M dtor" << endl; }
};
struct C {
M m_;
C() { cout << "C ctor" << endl; throw exception(); }
~C() { cout << "C dtor" << endl; }
};
auto main() -> int {
try {
C c;
}
catch (exception& e) {
cout << e.what() << endl;
}
}
输出为;
M ctor
C ctor
M dtor
std::exception
如果要动态分配M m_
成员,优先使用unique_ptr
或shared_ptr
而不是裸指针,并允许智能指针为您管理对象;如下;
#include <iostream>
#include <memory>
using namespace std;
struct M {
M() { cout << "M ctor" << endl; }
~M() { cout << "M dtor" << endl; }
};
struct C {
unique_ptr<M> m_;
C() : m_(new M()) { cout << "C ctor" << endl; throw exception(); }
~C() { cout << "C dtor" << endl; }
};
这里的输出反映了上面的输出。
如果您抛出一个构造函数,则不会构造该对象,因此您需要负责删除已分配的资源。这更进一步!考虑这个代码
int a = function(new A, new A);
A 分配的AND 构造的顺序由编译器决定。如果您的 A 构造函数可以抛出,您可能会以内存泄漏告终!
编辑:
改用
try{
auto first = std::make_unique<A>();
auto second = std::make_unique<A>();
int a = function(*first, *second);
...
当您编写 Foo* result = new Foo()
时,编译器将其翻译为以下代码的等价物:
void* temp = operator new(sizeof(Foo)); // allocate raw memory
try {
Foo* temp2 = new (temp) Foo(); // call constructor
result = temp2;
} catch (...) {
operator delete(temp); // constructor threw, deallocate memory
throw;
}
因此,如果构造函数抛出,您无需担心分配的内存。但是请注意,这不适用于在构造函数中分配的额外内存。析构函数只对构造函数完成的对象调用,所以你应该立即将所有分配到小包装对象(智能指针)中。
所以我们有一个构造函数可以根据传递给它的参数抛出异常,但我们不知道如果发生这种情况如何删除对象。代码重要部分:
try
{
GameBase *gameptr = GameBase::getGame(argc, argv);
if (gameptr == 0)
{
std::cout << "Correct usage: " << argv[PROGRAM_NAME] << " " << "TicTacToe" << std::endl;
return NO_GAME;
}
else
{
gameptr->play();
}
delete gameptr;
}
catch (error e)
{
if (e == INVALID_DIMENSION)
{
std::cout << "Win condition is larger than the length of the board." << std::endl;
return e;
}
}
catch (...)
{
std::cout << "An exception was caught (probably bad_alloc from new operator)" << std::endl;
return GENERIC_ERROR;
}
在第三行,GameBase::getGame()
调用派生自 GameBase
的其中一个游戏的构造函数和 returns 指向该游戏的指针,这些构造函数可以抛出异常。问题是,如果发生这种情况,我们如何才能删除 gameptr
指向的(部分?)对象?如果抛出异常,我们将退出 gameptr
的范围,因为我们离开了 try 块,无法调用 delete gameptr
.
要评估异常安全性,您需要在 GameBase::getGame
中提供对象构造的更多详细信息。
规则是这样的,如果构造函数抛出异常,则不会创建对象,因此不会调用析构函数。关联的内存分配也被释放(即对象本身的内存)。
那么问题就变成了,一开始内存是怎么分配的?如果它带有 new GameBase(...)
,则无需释放或删除结果指针 - 内存由运行时释放。
为了清楚地说明已经构造的成员变量发生了什么;它们在 "parent" 对象异常时被破坏。考虑 sample code;
#include <iostream>
using namespace std;
struct M {
M() { cout << "M ctor" << endl; }
~M() { cout << "M dtor" << endl; }
};
struct C {
M m_;
C() { cout << "C ctor" << endl; throw exception(); }
~C() { cout << "C dtor" << endl; }
};
auto main() -> int {
try {
C c;
}
catch (exception& e) {
cout << e.what() << endl;
}
}
输出为;
M ctor
C ctor
M dtor
std::exception
如果要动态分配M m_
成员,优先使用unique_ptr
或shared_ptr
而不是裸指针,并允许智能指针为您管理对象;如下;
#include <iostream>
#include <memory>
using namespace std;
struct M {
M() { cout << "M ctor" << endl; }
~M() { cout << "M dtor" << endl; }
};
struct C {
unique_ptr<M> m_;
C() : m_(new M()) { cout << "C ctor" << endl; throw exception(); }
~C() { cout << "C dtor" << endl; }
};
这里的输出反映了上面的输出。
如果您抛出一个构造函数,则不会构造该对象,因此您需要负责删除已分配的资源。这更进一步!考虑这个代码
int a = function(new A, new A);
A 分配的AND 构造的顺序由编译器决定。如果您的 A 构造函数可以抛出,您可能会以内存泄漏告终!
编辑: 改用
try{
auto first = std::make_unique<A>();
auto second = std::make_unique<A>();
int a = function(*first, *second);
...
当您编写 Foo* result = new Foo()
时,编译器将其翻译为以下代码的等价物:
void* temp = operator new(sizeof(Foo)); // allocate raw memory
try {
Foo* temp2 = new (temp) Foo(); // call constructor
result = temp2;
} catch (...) {
operator delete(temp); // constructor threw, deallocate memory
throw;
}
因此,如果构造函数抛出,您无需担心分配的内存。但是请注意,这不适用于在构造函数中分配的额外内存。析构函数只对构造函数完成的对象调用,所以你应该立即将所有分配到小包装对象(智能指针)中。