C++ 谁删除在构造函数中出现异常的“新建”操作期间分配的内存?
我真不敢相信我找不到一个明确的答案 < >如何释放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
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:你想避免什么内存泄漏?。。。注意,抛出的类的析构函数将不会被调用…-非常重要的一点。你的意思是如果一个物体不能完成构造。。。?