我知道这个问题的标题看起来有点伤脑筋,但我真的不知道如何用一句话来问这个问题。我只会告诉你我的意思:
void f(T *obj)
{
// bla bla
}
void main()
{
f(new T());
}
据我所知,(几乎)每个新的都需要一个删除,这需要一个指针(由新返回)。在这种情况下,new 返回的指针不会存储在任何地方。那么这会是内存泄漏吗?
C++ 是否有某种魔法(程序员不可见)在函数结束后删除对象,或者这种做法总是一个坏主意?
没有特别的魔法,删除不会被自动调用。
这绝对不是“总是一个坏主意” - 如果函数以某种形式获得对象的所有权,那么它是调用此类函数的完全有效方式:
container.AddAndTakeOwnership(new MyItem(42));
是的,这总是一个坏主意。函数和构造函数应该明确它们的所有权语义,并且如果它们期望获取或共享传递的指针的所有权,它们应该分别接收std::unique_ptr
或共享std::shared_ptr
。
有许多遗留 API 采用具有所有权语义的原始指针,即使在标准库中也是如此(例如,locale
构造函数采用Facet *
所有权),但任何新代码都应该避免这种情况。
即使在构造 a unique_ptr
or时,shared_ptr
您也可以避免使用new
关键字,在后一种情况下使用make_shared
,在前一种情况下编写应该很快添加到语言中的make_unique
函数模板。
在这种情况下,new 返回的指针不会存储在任何地方。那么这会是内存泄漏吗?
不,它不一定是内存泄漏。指针作为参数存储为f
:
void f(T *obj)
// ^^^ here pointer is "stored"
{
// bla bla
delete obj; // no memory leak if delete will be called on obj
}
void main()
{
f(new T());
// ^^^^^^^ this "value" will be stored as an argument to f
}
C++ 是否有某种魔法(程序员不可见)在函数结束后删除对象,或者这种做法总是一个坏主意?
在你的例子中没有魔法。正如我所展示的 - 必须明确调用删除。
更好的是使用智能指针,然后 C++“魔术”工作,并且不需要删除。
void f(std::unique_ptr<T> obj)
{
// bla bla
}
void main()
{
f(new T());
}
显示的代码将导致内存泄漏。C++ 没有垃圾收集,除非您明确使用专门的框架来提供它。
其原因与 C/C++ 中管理内存的方式有关。对于局部变量,例如您的示例,直接从操作系统(malloc)请求对象的内存,然后指向对象的指针存在于堆栈中。由于 C/C++ 可以进行任意复杂的指针运算,编译器无法知道是否存在指向该对象的其他指针,因此在函数 f() 结束时无法回收内存。
为了自动防止泄漏,必须从托管堆中分配内存,并且必须仔细跟踪对该堆的每个引用以确定何时不再使用给定对象。为了获得这种能力,你必须放弃 C 的指针算术能力。
例如,假设编译器可以神奇地发现对 obj 的所有正常引用都已失效并删除了该对象(释放了内存)。如果你有一些非常复杂的 RUNTIME DEPENDENT 表达式,比如 void* ptr = (&& &&(&&& *obj)/2++ - currenttime() - 567 + 3^2 % 52) 等;编译器如何知道这个 ptr 是否指向 obj?没有办法知道。这就是没有垃圾收集的原因。您可以进行垃圾收集或复杂的运行时指针运算,而不是两者兼而有之。
这通常在您的函数 f() 将对象存储在某处时使用,例如数组(或任何其他数据容器)或简单的类成员;在这种情况下,删除将(并且必须)在其他地方进行。
否则不是一个好主意,因为无论如何您都必须在函数结束时手动删除它。在这种情况下,您只需声明一个自动(在堆栈上)对象并通过指针传递它。
没有魔法。在您的情况下, f 被称为 main 返回 CRT 的 main 后,操作系统最终将清理“泄漏”。这不一定是一个坏主意,它可能会赋予 f 所有权,并且由 f 来做事并最终删除。有些人称之为不好的做法,但它可能在野外。
编辑:虽然我认为该代码不比以下更危险:
void f(T *obj)
{
// bla bla
}
void main()
{
T* test = new T ();
f(test);
}
主要是一样的。它对 f 说,这是一个指向某个内存的指针,它是你的,你现在照顾它。
在 C++ 中,不鼓励传递指针,因为没有相关的所有者语义(即,您无法知道谁拥有指针,因此谁负责删除指针)。因此,当您确实有一个函数(需要一个指针)时,您需要非常清楚地记录该函数是否负责清理指针。当然,像这样的文档很容易出错,因为用户必须阅读它。
在 C++ 程序中,传递描述事物所有权语义的对象更为正常。
通过使用这些技术,您不仅可以记录所有权语义,而且使用的对象还将自动控制对象的生命周期(从而使您的函数免于调用 delete)。
delete
因此,在现代 C++ 代码中很少看到手动调用。
所以我会这样写:
void f(std::unique_ptr<T> obj)
{
// bla bla
}
int main()
{
f(std::unique_ptr<T>(new T()));
}