C++中的'new'和'delete'是否已经被弃用?

70

我偶然发现了一个涉及不同大小的数组声明的测验。我想到的第一件事是,我需要使用new命令进行动态分配,就像这样:

while(T--) {
   int N;
   cin >> N;
   int *array = new int[N];
   // Do something with 'array'
   delete[] array;
}

不过,我发现其中一种解决方案允许以下情况:

while(T--) {
    int N;
    cin >> N;
    int array[N];
    // Do something with 'array'
}

经过一番调查,我发现g++允许这样做,但这让我想了一下,在哪些情况下需要使用动态分配呢?还是说编译器将其转换为动态分配呢?

delete函数已经包括在内。请注意,这里的问题不是关于内存泄漏的。


55
第二个例子使用了C++中从未出现过的可变长度数组。对于这种情况,请改用std::vectorstd::vector<int> array(N);)。 - Some programmer dude
7
直接回答你的问题应该是:不,它并没有被淘汰。虽然现代C++版本提供了许多简化内存所有权管理的功能(智能指针),但直接调用“new OBJ”来分配对象仍然是常见的做法。 - pptaszni
9
对于其他对为什么人们谈论内存泄漏感到困惑的人,这个问题已经被编辑以纠正一个对问题并不重要的错误。 - Mike Caron
4
@Mannoj 更喜欢使用“动态”和“自动”这些术语来代替“堆”和“栈”。虽然很少见,但是可以实现不使用堆和栈的C++程序。 - user4581301
2
我不确定那是不是一个玩笑。C++已经正式弃用了一些东西,但由于某种原因,它们通常会非正式地继续存在。 - user4581301
显示剩余9条评论
7个回答

118

你展示的任何代码片段都不是现代C++代码的典范。

newdelete(以及new[]delete[])在C++中没有过时,而且永远不会过时。它们仍然是动态分配对象的关键方式。但是,由于您必须始终将newdelete(以及new[]delete[])匹配,因此最好将其保留在能够为您确保这一点的(库)类中。详见为什么C++程序员应该尽量减少使用'new'?

你的第一个代码片段使用了“裸”的new[],然后从未delete[]创建的数组。这是一个问题。std::vector可以很好地完成你需要的所有操作。它将在幕后使用某种形式的new(我不会深入研究实现细节),但是对于你所关心的一切,它是一个更好、更安全的动态数组。

你的第二个代码片段使用了可变长度数组(VLA),这是C语言的一个特性,一些编译器也将其作为扩展功能引入到了C++中。与new不同,VLA本质上是分配在栈上(一种非常有限的资源)。但更重要的是,它们不是标准的C++特性,应该避免使用,因为它们不可移植。它们肯定不能替代动态(即堆)分配。


3
我想补充一点,尽管可变长度数组(VLAs)在官方标准中并未正式支持,但它们被所有主要编译器支持。因此,避免使用 VLAs 的决定更多是出于风格/偏好考虑,而不是真正的可移植性问题。 - Stack Tracer
4
请记住,您不能返回数组或将其存储在其他地方,因此VLA永远不会超出函数执行的时间。 - Ruslan
16
据我所知,MSVC不支持可变长度数组(VLA)。而MSVC无疑是一款“主流编译器”。 - Max Langhof
2
“你必须始终将new与delete匹配” - 但如果你使用Qt,由于其基类都有垃圾回收器,所以大多数情况下你只需使用new并忘记它。对于GUI元素,当父窗口关闭时,子元素会自动超出范围并进行垃圾回收。 - vsz
6
即使在Qt中,每个new仍然都有对应的delete;只是这些delete是由父窗口小部件执行的,而不是在与new相同的代码块中执行。 - jjramsey
显示剩余8条评论

25
首先,new/delete并没有被弃用。
对于你的特定情况,它们不是唯一的解决方案。你选择哪个取决于在"对数组执行某些操作"的注释下隐藏的内容。
您的第二个示例使用了非标准的VLA扩展,尝试将数组放在堆栈上。这有一定的局限性-即有限的大小和在数组超出范围后无法使用此内存。您不能将其移出,它会在堆栈展开后“消失”。
因此,如果您的唯一目标是进行本地计算,然后丢弃数据,那么实际上可能会很好用。但是,更健壮的方法是动态分配内存,最好使用std::vector。这样,您就可以基于运行时值(这正是我们一直在寻找的)为确切数量的元素创建空间,但它也会自动清理干净,如果需要保留内存以供以后使用,可以将其移出此范围。
回到开始,vector可能会在更深层次上使用new,但您不应该担心,因为它呈现的接口要优于其他方式。在这个意义上,使用newdelete被认为是不推荐的。

2
请注意“...few layers deeper”。如果您要实现自己的容器,仍应避免使用newdelete,而是使用像std::unique_pointer这样的智能指针。 - Max
3
std::unique_ptr 的默认析构函数会调用 deletedelete[],这意味着所拥有的对象必须是由 newnew[] 分配而来的,而这些函数在 C++14 中被封装进了 std::make_unique 函数中。 - Laurent LA RIZZA
当然,那是“默认”删除器的行为。您可以使用其他内容创建基于unique_ptr的类型,而我的FILE *处理程序一直都是以下结构体的实现struct fcloser { void operator()(FILE* file) { std::fclose(file); }};auto open(char const* path, char const* mode = "rb") {return std::unique_ptr<FILE, fcloser>{std::fopen(path, mode)};} - Marcin Zdun

16
你的第二个例子使用了可变长数组(VLAs),这实际上是C99而不是 C++!)的一个特性,但仍然被g++支持。
另请参见这个答案
请注意,可变长数组与new/delete不同,不会以任何方式“废弃”它们。
还要注意,VLAs 不是 ISO C++。

15

现代C++提供了更简单的方法来处理动态分配。智能指针可以负责异常情况下的清理(如果允许,这可能发生在任何地方)和早期返回,一旦引用的数据结构超出作用域,因此使用这些智能指针可能是有意义的:

  int size=100;

  // This construct requires the matching delete statement.
  auto buffer_old = new int[size];

  // These versions do not require `delete`:
  std::unique_ptr<int[]> buffer_new (new int[size]);
  std::shared_ptr<int[]> buffer_new (new int[size]); 
  std::vector<int> buffer_new (size);  int* raw_access = buffer_new.data();

从C++14开始,您还可以编写

auto buffer_new = std::make_unique<int[]>(size);

如果分配失败,这样看起来更好,并且可以防止内存泄漏。从C++20开始,您应该能够做到这一点。

auto a = std::make_shared<int[]>(size);

截至撰写本文时,对我而言,这仍无法使用gcc 7.4.0编译通过。在这两个示例中,我们还使用auto而不是左侧的类型声明。在所有情况下,按照惯例使用数组:

buffer_old[0] = buffer_new[0] = 17;

内存泄漏和重复delete导致的崩溃是C ++多年来一直受到抨击的问题,成为转向其他语言争论的"中心点"。也许最好避免。


你应该避免使用unique/shared_ptr的构造函数,而是选择make_unique/shared,这样不仅不需要两次写入被构造的类型(使用auto),而且如果构造过程中失败了(如果你正在使用一个可能会失败的类型),就不会泄露内存或资源。 - Simon Buchan
3
从C++14开始,make_unique可用于数组,而仅从C++20开始,make_shared才可用于数组。 然而,这仍然很少是默认设置,因此建议使用std::make_shared<int []>(size)似乎有些超前。 - Audrius Meškauskas
好的!我并不经常使用make_shared<int[]>,因为你几乎总是需要vector<int>,但知道这个也很好。 - Simon Buchan
过于追求细节,但如果我没记错的话,unique_ptr 构造函数是不抛出异常的,因此对于具有不抛出异常构造函数的 T,使用 unique_ptr(new int[size]) 不会有泄漏风险,而 shared_ptr 则具有以下特性:“如果抛出异常,则在 T 不是数组类型时调用 delete p,在其他情况下调用 delete[] p。”,因此你可以获得相同的效果——风险在于 unique/shared_ptr(new MyPossiblyAllocatingType[size]) - Simon Buchan

3

newdelete不会被废弃。

new操作符创建的对象可以通过引用传递。使用delete删除对象。

newdelete是该语言的基础方面。可以使用newdelete来管理对象的持久性。这些功能绝对不会被废弃。

语句 - int array[N] 是定义数组的一种方式。数组可以在封闭代码块的范围内使用。无法像将对象传递给另一个函数那样传递数组。


2
第一个例子需要在结尾处使用delete[],否则将会出现内存泄漏。
第二个例子使用了C++不支持的可变长度数组;它只允许使用常量表达式指定数组长度
在这种情况下,使用std::vector<>是一个有用的解决方案;它将所有可以在数组上执行的操作封装到一个模板类中。

3
“until C++11”是什么意思?我非常确定,可变长度数组(VLAs)从未成为标准的一部分。 - Lukas-T
查看C++14标准第184页第8.3.4段。 C++14标准 - Zig Razor
4
那不是标准,只是一份草案,至于“运行时大小的数组”部分似乎没有被包含在标准中。 cppreference 上也没有提到可变长数组。 - Lukas-T
1
@zigrazor cppreference.com网站上有一个链接列表,其中包含每个标准发布前/后最接近的草案。已发布的标准不是免费提供的,但这些草案应该非常接近。从文档编号可以看出,您链接的草案是C++14的一份较旧的工作草案。 - walnut
2
@learning_dude 这不被标准支持。答案是(现在)正确的(虽然简短)。它只能在GCC允许作为非标准扩展时才能工作。 - walnut
显示剩余2条评论

-4

语法看起来像C++,但习惯用法类似于老式的Algol60。常见的代码块如下:

read n;
begin
    integer array x[1:n];
    ... 
end;

这个例子可以写成:

while(T--) {
    int N;
    cin >> N;
    {
        int array[N];
        // Do something with 'array'
    }
}

有时候我在现有的编程语言中会想念这个功能 ;)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接