如果构造函数抛出异常,如何删除对象



因此,我们有一个构造函数,它可以根据传递给它的参数抛出异常,但如果发生这种情况,我们不知道如何删除对象。代码的重要部分:

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派生的一个游戏的构造函数,并返回该游戏的指针,这些构造函数可以抛出异常。问题是,如果发生这种情况,我们如何删除gameptr指向的(部分?)对象?如果抛出异常,我们将退出gameptr的作用域,因为我们离开了try块,无法调用delete gameptr

要评估异常安全性,需要在GameBase::getGame中提供对象构造的更多细节。

规则是通过的,如果构造函数抛出,则不会创建对象,因此不会调用析构函数。关联的内存分配也会被解除分配(即对象本身的内存)。

然后问题就变成了,内存是如何分配的?如果是new GameBase(...),则不需要解除分配或删除结果指针——内存由运行时解除分配。


为了清楚已经构建的成员变量会发生什么;它们在"父"对象的例外情况下被销毁。考虑示例代码;

#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_ptrshared_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; }
};

此处的输出反映了上面的输出。

当您编写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;
}

因此,如果构造函数抛出,您不需要担心分配的内存。但是,请注意,这并不适用于在构造函数中分配的额外内存。只有构造函数完成的对象才会调用析构函数,所以应该立即将所有分配分配到小包装器对象(智能指针)中。

如果抛出构造函数,则不会构造对象,因此,您要负责删除分配的资源。这更进一步!考虑此代码

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);
...

最新更新