C++ 谁删除在构造函数中出现异常的“新建”操作期间分配的内存?

C++ 谁删除在构造函数中出现异常的“新建”操作期间分配的内存?,c++,exception,memory-leaks,constructor,C++,Exception,Memory Leaks,Constructor,我真不敢相信我找不到一个明确的答案 < >如何释放C++类构造函数抛出异常后分配的内存,在使用新运算符初始化的情况下。例如: class Blah { public: Blah() { throw "oops"; } }; void main() { Blah* b = NULL; try { b = new Blah(); } catch (...) { // What now? } } 当我尝试这个方法时,catch块中的b

我真不敢相信我找不到一个明确的答案

< >如何释放C++类构造函数抛出异常后分配的内存,在使用新运算符初始化的情况下。例如:

class Blah
{
public:
  Blah()
  {
    throw "oops";
  }
};

void main()
{
  Blah* b = NULL;
  try
  {
    b = new Blah();
  }
  catch (...)
  {
    // What now?
  }
}
当我尝试这个方法时,catch块中的b是NULL,这是有意义的

调试时,我注意到conrol在到达构造函数之前进入内存分配例程

请访问MSDN网站:

当使用new来分配内存时 对于C++类对象,对象 在内存之后调用构造函数 已分配

因此,请记住,局部变量b从未赋值,即在catch块中为NULL。如何删除已分配的内存

在这方面获得跨平台的答案也很好。即,C++规范说什么?< /P> 澄清:我不是说类在c'tor中分配了内存,然后抛出。我很感激在这种情况下,不会打电话给检察官。在我的例子中,我用内存分配对象BLAH。< /P> < P>引用C++ FAQ:

[17.4]如果我的构造函数可能抛出 例外情况? 对象中的每个数据成员都应该清理自己的混乱

如果构造函数抛出异常,则对象的析构函数不会被调用 跑如果您的对象已经完成了需要撤消的操作 例如分配一些内存、打开文件或锁定 信号灯,这些需要撤销的东西必须记住 由对象内部的数据成员创建

例如,而不是将内存分配到原始数据中 成员,将分配的内存放入智能指针成员对象, 这个智能指针的析构函数将删除Fred 当智能指针死亡时。模板std::auto_ptr是一个 例如,智能指针。你也可以。你也可以

顺便说一句,如果你认为你的Fred班会被分配 在智能指针中,对用户友好并创建typedef 在你的课堂上:


我认为构造函数引发异常有点奇怪。 你能有一个返回值并在你的main中测试它吗

class Blah
{
   public:

   Blah()
       {
           if Error
           {
              this.Error = "oops";
           }
        }
};

void main()
{
Blah* b = NULL;

b = new Blah();

if (b.Error == "oops")
{
   delete (b);
   b = NULL;
}

你应该参考类似的问题和答案。 基本上,如果构造函数抛出异常,那么对象本身的内存将再次被释放,这是安全的。尽管如此,如果在构造函数中声明了其他内存,则在异常离开构造函数之前,您需要自己释放它

对于谁删除内存的问题,答案是编译器生成的新运算符后面的代码。如果它识别出一个离开构造函数的异常,它必须调用类成员的所有析构函数,因为在调用构造函数代码之前已经成功构造了这些类成员,释放它们的内存可以与析构函数调用一起递归完成,最有可能的方法是对它们调用适当的delete,并释放分配给这个类本身的内存。然后它必须将捕获的异常从构造函数重新传递给new的调用方。
当然,可能还有更多的工作要做,但我无法从脑海中提取所有细节,因为它们取决于每个编译器的实现。

长短不一的是,如果您没有像示例中那样对对象中的其他实体进行任何分配,那么分配的内存将被删除自动地但是,任何新语句或直接管理内存的任何其他语句都需要在构造函数中的catch语句中进行处理,否则该对象将被删除,而不会删除其后续分配,而您,我的朋友,发生泄漏。

如果构造函数抛出为对象分配的内存,则会自动神奇地返回到系统

注意,不会调用抛出的类的析构函数。 但是基类构造函数已经完成的任何基类的析构函数也将被调用

注: 正如大多数其他人所指出的那样,成员可能需要进行一些清理

已完全初始化的成员将调用其析构函数,但如果您在析构函数中拥有任何原始指针成员(即删除),则必须在抛出之前进行一些清理,这是不在类中使用自有原始指针的另一个原因

#include <iostream>

class Base
{
    public:
        Base()  {std::cout << "Create  Base\n";}
        ~Base() {std::cout << "Destroy Base\n";}
};

class Deriv: public Base
{
    public:
        Deriv(int x)    {std::cout << "Create  Deriv\n";if (x > 0) throw int(x);}
        ~Deriv()        {std::cout << "Destroy Deriv\n";}
};

int main()
{
    try
    {
        {
            Deriv       d0(0);  // All constructors/Destructors called.
        }
        {
            Deriv       d1(1);  // Base constructor and destructor called.
                                // Derived constructor called (not destructor)
        }
    }
    catch(...)
    {
        throw;
        // Also note here.
        // If an exception escapes main it is implementation defined
        // whether the stack is unwound. By catching in main() you force
        // the stack to unwind to this point. If you can't handle re-throw
        // so the system exception handling can provide the appropriate
        // error handling (such as user messages).
    }
}

如果一个对象由于构造函数抛出异常而无法完成销毁,那么作为构造函数特殊处理的一部分,首先会发生这种情况,即所有要构造的成员变量都会被销毁-如果在初始值设定项列表中抛出异常,这意味着只有初始值设定项已完成的元素才会被销毁

然后,如果对象被分配了new,则相应的dealloca 使用传递给运算符new的相同附加参数调用函数运算符delete。例如,new std::nothrow somethinghatthrows将使用运算符new size_of_ob,nothrow分配内存,尝试构造somethinghatthrows,销毁成功构造的任何成员,然后在传播异常时调用运算符delete ptr_to_obj,nothrows-它不会泄漏内存


您需要注意的是,连续分配多个对象-如果后面的一个对象抛出,那么前面的对象将不会自动释放。最好的方法是使用智能指针,因为作为局部对象,在堆栈展开时调用析构函数,析构函数将正确地释放内存。

< P>从C++ 2003标准5.3.4/17-新:

如果上述对象初始化的任何部分通过抛出异常而终止,并且可以找到合适的释放函数,则调用释放函数以释放正在构造对象的内存,然后异常继续在新表达式的上下文中传播。如果找不到明确的匹配释放函数,则传播异常不会导致释放对象的内存。[注意:当被调用的分配函数不分配内存时,这是合适的;否则,可能会导致内存泄漏。]

因此,可能存在泄漏,也可能不存在泄漏-这取决于是否可以找到合适的deallocator(通常情况下是这样),除非运算符new/delete已被重写。在存在合适的deallocator的情况下,编译器负责在构造函数抛出时连接对它的调用


请注意,这或多或少与构造函数中获取的资源发生的情况无关,这是我第一次尝试给出答案时讨论的问题,这是许多常见问题解答、文章和帖子中讨论的问题。

使用荷兰语来说,所描述的问题就像通往罗马的道路一样古老。我已经解决了这个问题,可能引发异常的对象的内存分配如下所示:

try
{
    std::string *l_string =
        (_heap_cleanup_tpl<std::string>(&l_string),
        new std::string(0xf0000000, ' '));
    delete l_string;
}
catch(std::exception &)
{
}
class _heap_cleanup_helper
{
    public:
    _heap_cleanup_helper(void **p_heap_block) :
        m_heap_block(p_heap_block),
        m_previous(m_last),
        m_guard_block(NULL)
    {
        *m_heap_block = NULL;
        m_last = this;
    }
    ~_heap_cleanup_helper()
    {
        if (*m_heap_block == NULL) operator delete(m_guard_block);
        m_last = m_previous;
    }
    void **m_heap_block, *m_guard_block;
    _heap_cleanup_helper *m_previous;
    static _heap_cleanup_helper *m_last;
};

_heap_cleanup_helper *_heap_cleanup_helper::m_last;

template <typename p_alloc_type>
class _heap_cleanup_tpl : public _heap_cleanup_helper
{
    public:
    _heap_cleanup_tpl(p_alloc_type **p_heap_block) :
        _heap_cleanup_helper((void **)p_heap_block)
    {
    }
};
void *operator new (size_t p_cbytes)
{
    void *l_retval = malloc(p_cbytes);

    if (
        l_retval != NULL &&
        *_heap_cleanup_helper::m_last->m_heap_block == NULL &&
        _heap_cleanup_helper::m_last->m_guard_block == NULL
    )
    {
        _heap_cleanup_helper::m_last->m_guard_block = l_retval;
    }
    if (p_cbytes != 0 && l_retval == NULL) throw std::bad_alloc();

    return l_retval;
}

void operator delete(void *p_buffer)
{
    if (p_buffer != NULL) free(p_buffer);
}
用户定义的新运算符如下所示:

try
{
    std::string *l_string =
        (_heap_cleanup_tpl<std::string>(&l_string),
        new std::string(0xf0000000, ' '));
    delete l_string;
}
catch(std::exception &)
{
}
class _heap_cleanup_helper
{
    public:
    _heap_cleanup_helper(void **p_heap_block) :
        m_heap_block(p_heap_block),
        m_previous(m_last),
        m_guard_block(NULL)
    {
        *m_heap_block = NULL;
        m_last = this;
    }
    ~_heap_cleanup_helper()
    {
        if (*m_heap_block == NULL) operator delete(m_guard_block);
        m_last = m_previous;
    }
    void **m_heap_block, *m_guard_block;
    _heap_cleanup_helper *m_previous;
    static _heap_cleanup_helper *m_last;
};

_heap_cleanup_helper *_heap_cleanup_helper::m_last;

template <typename p_alloc_type>
class _heap_cleanup_tpl : public _heap_cleanup_helper
{
    public:
    _heap_cleanup_tpl(p_alloc_type **p_heap_block) :
        _heap_cleanup_helper((void **)p_heap_block)
    {
    }
};
void *operator new (size_t p_cbytes)
{
    void *l_retval = malloc(p_cbytes);

    if (
        l_retval != NULL &&
        *_heap_cleanup_helper::m_last->m_heap_block == NULL &&
        _heap_cleanup_helper::m_last->m_guard_block == NULL
    )
    {
        _heap_cleanup_helper::m_last->m_guard_block = l_retval;
    }
    if (p_cbytes != 0 && l_retval == NULL) throw std::bad_alloc();

    return l_retval;
}

void operator delete(void *p_buffer)
{
    if (p_buffer != NULL) free(p_buffer);
}

无论如何,你不应该在施工现场做重物搬运。把它留给某种init方法,你是安全的。如果构造函数抛出对象,那么对象内存已经被取消分配,只需小心处理成员,因为不会调用析构函数。相反。除非由于某种原因不能使用异常,否则对象应该在构造函数中完全初始化。阅读C++编程语言的附录E以获得更多的细节:@ JLDUPONT:我希望我们可以投票否决你的评论。构造函数使对象处于随时可用状态,因此我们不必记住调用任何init函数。你的建议是,IMO,非常糟糕。但是,谷歌的C++准则更喜欢错误代码到异常代码,所以对于它们来说,它是有意义的,因为它们无法从构造函数报告失败。然而,我认为原始海报明确询问了分配给放置对象的内存。没有回答问题。这解决了一个相关问题,而不是所问的问题。这将告诉您如何编写Blah,以便Blah可以在抛出异常之前清理它分配的任何内容。问题是谁清理为Blah本身分配的内存。在这种情况下,一个例外是正确的工具。只有在不能使用异常的情况下才可以使用设置标志值。老实说,我认为这会更糟糕,因为用户不清楚当构造函数成功时,对象仍然处于不一致的状态。让别人来收拾你的烂摊子可不是什么好作风。如果无法从构造函数中获取导致错误的内容,我更希望使用异常方法。必须测试的返回值正是我们不希望的!这就是我们三十年来在纯旧C中所拥有的,我们现在有几十亿被忽略的返回值???标准的方法是例外,没有什么奇怪的。这样的返回值在历史上并不成功。除了例外,我们要么有可用的对象,要么没有可用的对象,因此我们可以使用它,而无需进行可能会忘记的测试。内存到底是如何释放的?我特别感兴趣的是,有人可能正在使用自己的运算符new,例如,您自己的运算符new和delete实现只需要知道如何分配和释放内存。额外的工作由编译器完成。当你说“新建”之类的话时,编译器会生成代码,1调用只分配内存的新运算符,2调用c'tor,3调用delete运算符(如果出现问题)。@Adrian:只是为了学究;-如果1次完成和2次投掷,则呼叫3次。太好了,我以为我疯了:-。Tha
请原谅我问了一个已经涉及到的问题-我确实先快速看了一下,但看不到这个特定的问题。这充其量只是一个特定的实现是如何做到的。如果丢失一小段内存(如“sizeofstd::string”)值得担心的话,当然可能会更简洁。让你的用户在他的电脑里多放一些千兆字节的内存!您似乎在暗示所有这些样板代码都是避免内存泄漏所必需的。正如对这个长达两年的问题的其他答案所表明的那样,这是没有必要的。或者您正在详细说明编译器可能插入的代码,以防止内存泄漏。在这种情况下,这完全是太详细了。。。一个非常简单的try/catch块也可以说明这一点。不管怎样,这都是一个糟糕的答案。我的答案是唯一解决避免内存泄漏的答案,而不是避免通过构造函数边界抛出异常的答案。你可能会发现答案很糟糕,但这是解决问题的唯一答案。只要为每个“新分配”添加一个简单的语句,问题就解决了。最后两个代码块只需放入一次。事实上,如果你不采取预防措施,C++允许内存泄漏进入你的程序。从可接受的答案来看:基本上,如果构造函数抛出异常,那么你就可以安全地再次释放对象本身的内存。从下一个投票率最高的答案:然后,如果对象被分配了new,则使用传递给操作符new的相同附加参数调用相应的释放函数operator delete。从下一个投票率最高的答案来看:如果构造函数抛出为对象分配的内存,则会自动神奇地返回到系统。gcc:你想避免什么内存泄漏?。。。注意,抛出的类的析构函数将不会被调用…-非常重要的一点。你的意思是如果一个物体不能完成构造。。。?