Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/163.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 析构函数如何知道何时激活自身?可以信赖吗?_C++_Memory Leaks_Destructor - Fatal编程技术网

C++ 析构函数如何知道何时激活自身?可以信赖吗?

C++ 析构函数如何知道何时激活自身?可以信赖吗?,c++,memory-leaks,destructor,C++,Memory Leaks,Destructor,例如,假设我有以下代码(纯示例): 在returnnew()中,函数返回后(当retval超出范围时),retval会被销毁吗?或者它会在我返回地址后禁用自动销毁,并且我可以说deletefoo;在main()的末尾?或者,以类似的方式(伪代码): 析构函数会去哪里?我应该说删除在哪里?还是这种未定义的行为?唯一可能的解决方案是使用STL引用的计数指针吗?这将如何实施 谢谢-我已经使用C++了一段时间,但从来没有过这种情况,不想创建内存泄漏。 对于堆栈创建的对象,析构函数在对象超出范围时自动调用

例如,假设我有以下代码(纯示例):

在returnnew()中,函数返回后(当retval超出范围时),retval会被销毁吗?或者它会在我返回地址后禁用自动销毁,并且我可以说deletefoo;在main()的末尾?或者,以类似的方式(伪代码):

析构函数会去哪里?我应该说删除在哪里?还是这种未定义的行为?唯一可能的解决方案是使用STL引用的计数指针吗?这将如何实施


谢谢-我已经使用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;
}