比如说我有以下代码(纯粹的例子):
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;
}
在returnnew()中,在函数返回后(当retval超出范围时)会对retval进行破坏吗?或者它会在我返回地址后禁用自动销毁,我可以说删除foo;在main()的末尾?或者,以类似的方式(伪代码):
void foo(void* arg) {
bar = (a*)arg;
//do stuff
exit_thread();
}
int main() {
while(true) {
a asdf;
create_thread(foo, (void*)&asdf);
}
return 0;
}
析构函数会去哪里?在哪里我要说删除?或者这是未定义的行为?唯一可行的解决方案是使用STL引用计数指针吗?这将如何实施?
谢谢 - 我已经使用了C ++一段时间但从未遇到过这种情况,并且不想创建内存泄漏。
答案 0 :(得分:10)
对于堆栈创建的对象,当对象超出范围时,将自动调用析构函数。
对于在堆上创建的对象,只有在显式调用delete
时才会释放内存。
是否从函数返回堆栈创建对象的地址无关紧要。当项目超出范围时,仍将调用析构函数。
所以对于你的代码示例:
a * returnnew()
{
a retval;
return(&retval);
}
在代码跳转回调用a
的代码之前,会调用 returnnew()
的析构函数。您返回该对象的地址,但该地址指向内存中不再属于您的位置。
我要说删除哪里?
使用delete
时,您只能使用new
如果您使用delete[]
new[]
还是这种未定义的行为?
如果对不属于您的内存地址执行的操作将是未定义的行为。但这不是正确的代码。
唯一可行的解决方案是使用STL引用计数指针吗?
您可以按值返回对象,也可以在堆上创建新对象。您还可以通过参数将对象传递给函数,并要求函数更改它。
如何实施?
//Shows how to fill an object's value by reference
void fillA(a& mya)
{
mya.var = 3;
}
//Shows how to return a value on the heap
a* returnNewA()
{
//Caller is responsible for deleting the returned pointer.
return new a();
}
//Shows how to return by value, memory should not be freed, copy will be returned
a returnA()
{
return a();
}
答案 1 :(得分:1)
a * returnnew() {
a retval;
return(&retval);
}
此处,retval
具有自动存储持续时间,这意味着当语言超出范围时,语言将自动销毁它。您返回的地址是指一个不再存在的对象,并且尝试使用返回值将是一个错误。
当您想要控制对象的生命周期时,您必须使用new运算符来创建它。
a* returnnew()
{
a* retval = new a();
return retval;
}
在这里,您现在可以完全控制此a
的生命周期。它会一直存在,直到你明确delete
它,或者你的程序结束。
您还可以为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 ++最大的优势之一是当自动存储持续时间对象超出范围时,语言自动调用析构函数的方式。如果确保程序中的每个资源都归这样的对象所有,那么泄漏资源就会困难得多。
答案 2 :(得分:1)
作为一般规则,避免C ++内存泄漏的最简单方法是尽量避免使用new和delete。
如果必须有指向某事物的指针,请使用智能指针(例如boost scoped_ptr或shared_ptr)。这样你仍然可以在堆上拥有你的对象,但是当智能指针超出范围时,它将被调用解构器。否则,尝试确保在每种情况下调用delete都会很麻烦并导致大量额外代码。
答案 3 :(得分:0)
returnnew()将在返回时销毁变量,因为您正在返回指向局部变量的指针。如果你想从那里返回retval,请以dinamically方式分配它,例如:
a * returnnew() {
a * retval = new a;
return retval;
}
或者只是:
a * returnnew() {
return new a;
}
动态分配的内存没有作用域,因此在你这样说之前不会被释放,通过删除/删除[] / free,或者直到程序退出(和其他一些与问题无关的情况) 。在此之前有人评论之前,我的“直到你这样说”还包括共享/智能/等指针行为。
至于你的第二个代码&问题,如果你在线程之外分配变量,但只是从内部使用它,你可以让线程在不再需要它时解除分配(删除)它。但是,如果您计划从多个点访问该变量,并且无法猜测何时可以安全地销毁它,请务必使用智能或共享指针。
答案 4 :(得分:0)
+1给Brian的回答,只是想添加一个关于线程方面的评论。
创建线程的代码将破坏传递给线程函数的asdf对象而不管子进程,因为asdf在父堆栈上。在堆上创建asdf,或者按值传递。否则,父节点将销毁asdf,并使您的子线程指向父节点堆栈上的错误地址。在任何情况下都不好,析构函数或没有析构函数。你安全地将asdf传递给线程中的函数的唯一方法是先创建线程,asdf是ITS堆栈中的堆栈对象,而不是父堆栈。
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;
}
答案 5 :(得分:0)
如果是以下代码:
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;
}
};
这样使用它:
trace glb("global");
main() {
trace t1("top of main");
for(int i = 0; i < 10; ++i)
{
trace t2("inside for");
}
return 0;
}
结果可能让您感到惊讶。