C++ 析构函数如何知道何时激活自身?可以信赖吗?
例如,假设我有以下代码(纯示例): 在returnnew()中,函数返回后(当retval超出范围时),retval会被销毁吗?或者它会在我返回地址后禁用自动销毁,并且我可以说deletefoo;在main()的末尾?或者,以类似的方式(伪代码): 析构函数会去哪里?我应该说删除在哪里?还是这种未定义的行为?唯一可能的解决方案是使用STL引用的计数指针吗?这将如何实施C++ 析构函数如何知道何时激活自身?可以信赖吗?,c++,memory-leaks,destructor,C++,Memory Leaks,Destructor,例如,假设我有以下代码(纯示例): 在returnnew()中,函数返回后(当retval超出范围时),retval会被销毁吗?或者它会在我返回地址后禁用自动销毁,并且我可以说deletefoo;在main()的末尾?或者,以类似的方式(伪代码): 析构函数会去哪里?我应该说删除在哪里?还是这种未定义的行为?唯一可能的解决方案是使用STL引用的计数指针吗?这将如何实施 谢谢-我已经使用C++了一段时间,但从来没有过这种情况,不想创建内存泄漏。 对于堆栈创建的对象,析构函数在对象超出范围时自动调用
谢谢-我已经使用C++了一段时间,但从来没有过这种情况,不想创建内存泄漏。
<>对于堆栈创建的对象,析构函数在对象超出范围时自动调用。p> 对于在堆上创建的对象,只有在显式调用delete
时才会释放内存
是否从函数返回堆栈创建对象的地址并不重要。当项目超出范围时,仍将调用析构函数
因此,对于您的代码示例:
class a {
int * p;
public:
a() {
p = new int;
}
~a() {
delete p;
}
};
a * returnnew() {
a retval;
return(&retval);
}
int main() {
a * foo = returnnew();
return 0;
}
a * returnnew()
{
a retval;
return(&retval);
}
a
的析构函数在代码跳回调用returnnew()
的代码之前被调用。返回该对象的地址,但该地址指向内存中不再属于您的位置
我应该说删除在哪里
您仅在使用新的时使用删除
如果使用了new[]
还是这种未定义的行为
您对不属于您的内存地址所做的操作将是未定义的行为。但这不是正确的代码
唯一可能的解决方案是使用STL引用的计数指针吗
可以按值返回对象,也可以在堆上创建新对象。您还可以通过参数将对象传递给函数,并要求函数对其进行更改
这将如何实施
returnnew()将在返回时销毁该变量,因为您返回的是指向局部变量的指针。如果您想从那里返回retval,请进行动态分配,例如:
a * returnnew() {
a * retval = new a;
return retval;
}
或者只是:
a * returnnew() {
return new a;
}
动态分配内存没有作用域,因此在您通过delete/delete[]/free或程序退出(以及其他一些与问题无关的情况)这样说之前,不会解除分配。在这里发表评论之前,我的“直到你这么说”还包括共享/smart/etc指针行为
至于您的第二个代码和问题,如果您在线程外部分配变量,但仅从内部使用它,那么您可以在不再需要它时让线程释放(删除)它。但是,如果您计划从多个点访问变量,并且无法猜测何时可以安全地销毁它,那么一定要使用智能指针或共享指针。+1对于Brian的回答,只想添加一条关于线程方面的注释
创建线程的代码将销毁传递给线程函数的asdf对象,而不考虑子对象,因为asdf位于父堆栈上。在堆上创建asdf或按值传递。否则,父线程将销毁asdf,并使子线程指向父线程堆栈上的错误地址。在任何情况下都不好,析构函数或无析构函数。安全地将asdf传递给线程中的函数的唯一方法是首先创建线程,并且asdf是其堆栈中的堆栈对象,而不是父堆栈
void foo(void* arg) {
bar = (a*)arg; // child has reference to a parent stack address!
//do stuff
exit_thread();
}
int main() {
while(true) {
a asdf; // parent stack
create_thread(foo, (void*)&asdf); // parent and child diverge here, asdf auto-destroyed
}
return 0;
}
在这里,retval
具有自动存储持续时间,这意味着该语言在超出范围时将自动销毁它。您返回的地址引用了一个不再存在的对象,尝试使用返回值将是一个错误
如果希望对象的生存期由您控制,则必须使用新操作符来创建它
a* returnnew()
{
a* retval = new a();
return retval;
}
在这里,您现在可以完全控制此a
的生命周期。它将一直存在,直到您明确地删除它,或者您的程序结束
您还可以为a
类提供有意义的复制语义,并按值返回,在这种情况下,调用者将获得自己的副本,与原始副本不同。那么,你的来电者不在乎原稿何时消失
class a
{
int * p;
public:
a(a const& rhs)
{
p = new int(rhs.p)
}
a()
{
p = new int;
}
~a()
{
delete p;
}
};
现在,您可以将新的a
构建为现有a
的副本。因此,您的函数可以通过值返回a
,如下所示:
a returnnew()
{
a retval;
return retval;
}
在这里,retval的生存期将在函数返回时结束,它将被语言自动破坏,并且不会泄漏任何资源。你的来电者会有他自己的副本,有他自己的生命
根据我的经验,大多数类都应该具有合理的复制语义,您不应该害怕通过值传递和返回它们。这样做更简单,并且可以避免悬空指针问题
C++最大的优点之一是,当自动存储持续时间对象超出范围时,该语言会自动调用析构函数。如果您确保程序中的每一个资源都属于这样一个对象,那么泄漏资源的难度就会大得多 在以下代码的情况下:
void foo(void* arg) {
bar = (a*)arg;
//do stuff
exit_thread();
}
int main() {
while(true) {
a asdf;
create_thread(foo, (void*)&asdf);
}
return 0;
}
在while循环的右括号处调用析构函数。这意味着它将在循环的每一次迭代中被调用(并且将在下一次迭代中再次构造)
作为探索构造函数和析构函数和范围的细微差别的有用工具,请考虑使用下面的类来帮助您在将来自己回答这些问题:
class trace {
private:
std::string msg_;
public:
explicit trace(const std::string &msg) : msg_(msg) {
std::cerr << "Constructing: " << msg_ << std::endl;
}
~trace() {
std::cerr << "Destructing: " << msg_ << std::endl;
}
};
类跟踪{
私人:
std::字符串msg_;
公众:
显式跟踪(const std::string&msg):msg(msg){
STR::CURR 作为一般规则,避免C++中内存泄漏最简单的方法是避免我们
a returnnew()
{
a retval;
return retval;
}
void foo(void* arg) {
bar = (a*)arg;
//do stuff
exit_thread();
}
int main() {
while(true) {
a asdf;
create_thread(foo, (void*)&asdf);
}
return 0;
}
class trace {
private:
std::string msg_;
public:
explicit trace(const std::string &msg) : msg_(msg) {
std::cerr << "Constructing: " << msg_ << std::endl;
}
~trace() {
std::cerr << "Destructing: " << msg_ << std::endl;
}
};
trace glb("global");
main() {
trace t1("top of main");
for(int i = 0; i < 10; ++i)
{
trace t2("inside for");
}
return 0;
}