我有以下代码在函数调用中动态分配类对象:
#include <iostream>
class A
{
public:
A(){std::cout<<"Constructing..."<<std::endl;}
~A(){std::cout<<"Deconstructing..."<<std::endl;}
};
A & f()
{
std::cout<<"Calling f()"<<std::endl;
A * pa = new A();
std::cout<<"End calling f()"<<std::endl;
return *pa;
}
int main()
{
A & b = f();
}
输出为:
Calling f()
Constructing...
End calling f()
表示从未删除过类对象。
但是,如果我将main
中的行更改为:
A b = f();
输出是:
Calling f()
Constructing...
End calling f()
Deconstructing...
表示类对象已自动删除。
为什么分配对非引用的引用会改变其动态行为?
答案 0 :(得分:4)
new
创建的动态对象没有变化。在任何一种情况下都不会被破坏,因为它只能通过明确使用delete
来销毁。
在第二种情况下,您通过复制动态对象来创建第二个对象b
。是自动的,当它超出范围时会被破坏,所以你会看到一条Deconstructing...
消息。
由于它是由复制构造函数初始化的,而不是默认构造函数,因此您不会看到相应的Constructing...
消息。您可以添加一个复制构造函数来查看:
A(A const &){std::cout<<"Copying..."<<std::endl;}
给出输出
Calling f()
Constructing...
End calling f()
Copying...
Deconstructing...
通常,始终使用智能指针和其他RAII类型来管理动态资源,以避免内存泄漏和其他生命周期混淆问题。
答案 1 :(得分:2)
在
的情况下A & b = f();
b
指的是在堆上创建的对象。
在
的情况下A b = f();
b
创建堆上数据的副本(可能的移动)。当函数退出时(当b
超出范围时),这个被破坏。
在两种情况下,堆上的对象都是独立的。
为了进一步探讨这一点,添加复制构造函数将有助于A(A const&)
,然后打印一条消息,甚至使其成为private
,并注意编译错误有助于标记副本及其位置。
注意:当你new
函数中的一个对象然后不销毁它,或者不将指针移动到其他所有者时,你将泄漏内存。喜欢unique_ptr
或share_ptr
等库实用程序来管理资源(RAII)。
答案 2 :(得分:1)
当声明一个值类型时,它在堆栈上构造,编译器在超出范围时负责调用它的析构函数。但是,当您通过调用new
在堆上创建实例时,编译器无法知道何时不再需要该实例,因此,程序员可以间接调用其析构函数使用delete
。您对堆上的对象的引用这一事实并不能使编译器知道何时必须销毁该对象。