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

Ker*_*rry 5 c++ memory constructor pointers dynamic-memory-allocation

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

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;
}
Run Code Online (Sandbox Code Playgroud)

在第三行中,GameBase::getGame()调用派生自其中一个游戏的构造函数,GameBase并返回指向该游戏的指针,这些构造函数可以抛出异常.问题是,gameptr如果发生这种情况,我们怎样才能删除指向的(部分?)对象?如果抛出异常,我们将退出范围,gameptr因为我们离开try块而无法调用delete gameptr.

Nia*_*all 9

要评估异常安全性,您需要提供有关对象构造的更多详细信息GameBase::getGame.

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

那么问题就变成了,如何分配内存开始?如果它是a 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是;

M ctor
C ctor
M dtor
std::exception
Run Code Online (Sandbox Code Playgroud)

如果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; }
};
Run Code Online (Sandbox Code Playgroud)

这里的输出反映了上面的输出.

  • 正确.任何成员变量本身就是对象,如果父对象抛出异常,一旦构造就会被破坏. (2认同)
  • @ hr0m.如果你在一个语句中`new`几个对象,那么可能会有结果内存泄漏等.通常不建议这样做.如果构造函数有多个参数需要"new",那么最好创建它们并将它们分配给资源管理器(例如`std :: unique_ptr`,然后将它们"移动"到对象中(移动或分离).如果是任何例外,RAII机制将清理资源. (2认同)

Seb*_*edl 5

当您编写 时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;
}
Run Code Online (Sandbox Code Playgroud)

因此,如果构造函数抛出异常,您无需担心分配的内存。但请注意,这不适用于构造函数内分配的额外内存。仅对构造函数已完成的对象调用析构函数,因此您应该立即将所有分配放入小包装对象(智能指针)中。