【问题标题】:Are 'new' and 'delete' getting deprecated in C++?'new' 和 'delete' 在 C++ 中被弃用了吗?
【发布时间】:2020-05-06 07:41:31
【问题描述】:

我偶然发现了一个涉及不同大小的数组声明的测验。我首先想到的是我需要通过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++ 允许这样做,但它让我一直在思考,在哪些情况下需要使用动态分配?还是编译器将其翻译为动态分配?

包括删除功能。但是请注意,这里的问题与内存泄漏无关。

【问题讨论】:

  • 第二个例子使用了variable-length array,它从来都不是C++的一部分。对于这种情况,请改用 std::vector (std::vector<int> array(N);)。
  • 您的问题的直接答案应该是:不,它没有被弃用。尽管现代版本的 C++ 提供了许多简化内存所有权管理(智能指针)的功能,但通过直接调用 new OBJ 来分配对象仍然是常见的做法。
  • 对于其他对人们谈论内存泄漏的原因感到困惑的人,该问题已被编辑以纠正与问题无关的错误
  • @Mannoj 更喜欢使用术语动态和自动来堆和堆栈。这种情况很少见,但可以在没有堆和堆栈的情况下实现 C++。
  • 我不确定这是否是个玩笑。 C++ 已经正式弃用了一些东西,但出于某种原因,它们确实倾向于非正式地继续存在。

标签: c++ arrays dynamic-memory-allocation static-memory-allocation


【解决方案1】:

您展示的 sn-p 都不是惯用的现代 C++ 代码。

newdelete(以及 new[]delete[])在 C++ 中不会被弃用,也永远不会被弃用。它们仍然是实例化动态分配对象的方式。但是,由于您必须始终将newdelete 匹配(以及将new[]delete[] 匹配),因此最好将它们保存在(库)类中以确保为您服务。见Why should C++ programmers minimize use of 'new'?

您的第一个 sn-p 使用“裸”new[],然后从不使用 delete[]s 创建的数组。那是个问题。 std::vector 在这里可以满足您的一切需求。它将在幕后使用某种形式的new(我不会深入探讨实现细节),但您需要注意的是,它是一个动态数组,但更好更安全。

您的第二个 sn-p 使用“可变长度数组”(VLA),一些编译器还允许在 C++ 中作为扩展的 C 功能。与new 不同,VLA 本质上是在堆栈上分配的(资源非常有限)。但更重要的是,它们不是标准的 C++ 特性,应该避免使用,因为它们不可移植。它们当然不会取代动态(即堆)分配。

【讨论】:

  • 我想补充一点,虽然 VLA 并未正式纳入标准,但所有主要编译器都支持它们,因此是否避免它们的决定更多的是风格问题/偏好而不是对可移植性的现实关注。
  • 还请记住,您不能返回数组或将其存储在其他地方,因此 VLA 永远不会超过函数的执行时间
  • @StackTracer 据我所知,MSVC 不支持 VLA。而 MSVC 绝对是一个“主要编译器”。
  • "你必须总是匹配一个新的和一个删除的"——如果你使用Qt就不行,因为它的基类都有垃圾收集器,所以你只需使用new 并忘记它,大部分时间。对于 GUI 元素,当父小部件关闭时,子小部件会超出范围并自动被垃圾收集。
  • @vsz 即使在Qt中,每个new仍然有一个匹配的delete;只是deletes 是由父小部件完成的,而不是在与news 相同的代码块中。
【解决方案2】:

好吧,首先,new/delete 并没有被弃用。

不过,在您的具体情况下,它们并不是唯一的解决方案。您选择的内容取决于您的“使用数组做某事”评论下隐藏的内容。

您的第二个示例使用了一个非标准的 VLA 扩展,它试图将数组放入堆栈。这有一定的限制 - 即有限的大小和在数组超出范围后无法使用此内存。您不能将其移出,它会在堆栈展开后“消失”。

因此,如果您的唯一目标是进行本地计算然后丢弃数据,那么它实际上可能工作正常。但是,更强大的方法是动态分配内存,最好使用std::vector。这样,您就可以根据运行时值(这是我们一直以来的目标)为所需数量的元素创建空间,但它也会很好地清理自己,并且您可以将其移出如果您想保留内存以供以后使用,请使用此范围。

回到开头,vector 可能会使用更深几层的new,但您不必担心这一点,因为它提供的界面要好得多。从这个意义上说,不鼓励使用newdelete

【讨论】:

  • 请注意“......更深的几层”。如果你要实现自己的容器,你仍然应该避免使用newdelete,而应该使用像std::unique_pointer 这样的智能指针。
  • @Max : std::unique_ptr 的默认析构函数调用deletedelete[],这意味着拥有的对象肯定已经被newnew[] 分配了,这些调用有自 C++14 以来一直隐藏在 std::make_unique 中。
  • 当然,除了 default 删除器的行为。你可以用其他东西创建一个基于unique_ptr的类型,而我的goto 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)};}的化身
【解决方案3】:

您的第二个示例使用 variable length arrays (VLA),这实际上是 C99不是 C++!)功能,但仍受 g++ 支持。

另见this answer

请注意,可变长度数组与new/delete 不同,不要以任何方式“弃用”它们。

请注意,VLA不是 ISO C++。

【讨论】:

    【解决方案4】:

    现代 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;
    

    内存泄漏new 和双倍delete 崩溃是 C++ 多年来一直受到抨击的问题,它是切换到其他语言的争论的“中心点”。也许最好避免。

    【讨论】:

    • 你应该避免使用unique/shared_ptr 构造函数来支持make_unique/shared,你不仅不必编写两次构造类型(使用auto),而且你不会冒险泄漏内存或构建中途失败时的资源(如果您使用的类型可能会失败)
    • make_unique 可用于 C++14 中的数组,而 make_shared 仅适用于 C++20。这仍然很少是默认设置,因此建议 std::make_shared(size) 提前找了我。
    • 很公平!我并没有真正使用make_shared&lt;int[]&gt;,当你几乎总是想要vector&lt;int&gt;,但很高兴知道。
    • 过度迂腐,但 IIRC unique_ptr 构造函数是 nothrow,因此对于具有 nothrow 构造函数的 T,因此 unique_ptr(new int[size])shared_ptr 没有泄漏风险具有以下内容:“如果抛出异常,则在 T 不是数组类型时调用 delete p,否则调用 delete[] p。”,因此您具有相同的效果 - 风险在于 unique/shared_ptr(new MyPossiblyAllocatingType[size])
    【解决方案5】:

    newdelete 不会被弃用。

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

    new 和 delete 是该语言的基础方面。 可以使用 new 和 delete 管理对象的持久性。 这些绝对不会被弃用。

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

    【讨论】:

      【解决方案6】:

      第一个例子最后需要一个delete[],否则会出现内存泄漏。

      第二个例子使用了C++不支持的可变数组长度;它只允许数组长度的常量表达式

      在这种情况下,使用std::vector&lt;&gt; 作为解决方案很有用;它将您可以对数组执行的所有操作包装到模板类中。

      【讨论】:

      • “直到 C++11”是什么意思?我很确定,VLA 从未成为标准的一部分。
      • 查看第 184 页第 8.3.4 段的 c++14 标准 [c++14 标准](isocpp.org/files/papers/N3690.pdf)
      • 那不是 标准,但据我所知,这只是一个草案和关于“运行时绑定数组”的部分并没有成为标准。@987654322 @ 没有提到 VLA。
      • @zigrazor cppreference.com 有a list 链接到每个标准发布之前/之后最接近的草案。已发布的标准并非免费提供,但这些草案应该非常接近。从文档编号可以看出,您链接的草稿是 C++14 的一些较旧的工作草稿。
      • @learning_dude 标准支持。答案(现在)是正确的(尽管很短)。它只对你有用,因为 GCC 允许它作为 非标准 扩展。
      【解决方案7】:

      语法看起来像 C++,但习语类似于简单的旧 Algol60。有这样的代码块很常见:

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

      例子可以写成:

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

      我有时会在当前语言中错过这一点;)

      【讨论】:

        猜你喜欢
        • 2015-08-26
        • 1970-01-01
        • 1970-01-01
        • 2012-10-26
        • 2023-03-22
        • 2011-05-27
        • 2017-12-07
        • 1970-01-01
        • 2011-01-13
        相关资源
        最近更新 更多