【问题标题】:Why is it a bad idea to use 'new'? [duplicate]为什么使用“新”是个坏主意? [复制]
【发布时间】:2011-11-29 00:59:31
【问题描述】:

可能重复:
In C++, why should new be used as little as possible?

在 C++ 中实例化一个类时使用“new”真的是个坏主意吗? Found here.

我知道使用原始指针是不明智的,但是当它是如此糟糕的做法时,为什么还要使用'new'关键字呢?还是这样?

【问题讨论】:

  • 通常不需要new 具有值语义的类型。我想这就是杰瑞所说的。
  • 投票结束。这应该是对链接问题的评论线程,因为这个问题是基于对该答案的根本误解!一般来说,new 并不是这样糟糕的做法,也不是一个坏主意。您链接的答案建议删除new()s 以解决编译时错误:您不能将指针传递给不期望的函数!
  • 如果你在 C++ 中取消了“原始指针”,你就不会剩下太多了。
  • 过去 15 年的 C++ 库开发一直致力于寻找避免使用 new 的方法。显式内存管理很难做到。
  • @HansPassant:我认为更重要的是编写正确的手动分配的困难在于自动和手动 lifetime 之间的根本区别,以及绝大多数自动对象比手动对象更好地服务所有程序逻辑(很大程度上归功于聪明的库构建块)。顺便说一句,投票重新开放,因为链接的问题似乎有点不同(答案过于关注实现细节,而不是更相关的语言语义)。

标签: c++


【解决方案1】:

重点是new,就像怀孕一样,创建了一个手动管理的资源(即由),因此它带有责任 .

C++ 是一种用于编写库的语言,每当您看到责任时,“C++”方法就是编写一个库元素来处理这个责任,并且只处理这个责任。对于动态内存分配,那些库组件已经存在,统称为“智能指针”;您需要查看 std::unique_ptrstd::shared_ptr(或它们的 TR1 或 Boost 等效项)。

在编写这些单一职责构建块时,您确实需要说newdelete。但是您只这样做一次,并且仔细考虑并确保提供正确的复制、分配和销毁语义。 (从异常安全的角度来看,单一责任至关重要,因为一次处理多个单一资源是非常不可扩展的。)

一旦你把所有东西都分解成合适的构建块,你组合这些块到越来越大的代码系统中,但此时你不再需要承担任何手动责任,因为构建模块已经为您完成了这项工作。

由于标准库为绝大多数用例(动态数组、智能指针、文件句柄、字符串)提供资源管理类,关键是一个精心设计的 C++ 项目应该很少需要任何一种手动资源管理,其中包括使用new。您的所有处理程序对象要么是自动的(作用域),要么是其他类的成员,这些类的实例依次由某人限定或管理。

考虑到这一点,您应该说new 的唯一时间是您创建一个新的资源管理对象时;尽管即便如此,这并不总是必要的:

std::unique_ptr<Foo> p1(new Foo(1, 'a', -2.5));   // unique pointer
std::shared_ptr<Foo> p2(new Foo(1, 'a', -2.5));   // shared pointer
auto p3 = std::make_shared<Foo>(1, 'a', -2.5);    // equivalent to p2, but better

更新:我想我可能只解决了 OP 的一半问题。许多来自其他语言的人似乎都认为任何对象都必须用new-type 表达式进行实例化。在接近 C++ 时,这本身就是一种非常无益的心态:

C++ 中的关键区别在于对象lifetime,或“存储类”。这可以是以下之一:自动(作用域)、静态(永久)或动态(手动)。全局变量具有静态生命周期。绝大多数变量(在本地范围内声明为Foo x;)具有自动生命周期。我们使用new 表达式仅用于动态 存储。当从另一种 OO 语言转向 C++ 时,最重要的一点是大多数对象只需要具有自动生命周期,因此无需担心。

所以第一个认识应该是“C++很少需要动态存储”。我觉得这可能是 OP 问题的一部分。这个问题可能更好地表述为“动态分配对象真的是个坏主意吗?”。只有在你决定你真的需要动态存储之后,我们才会讨论你是否应该经常说newdelete,或者是否有更好的替代方案,是我原来回答的重点。

【讨论】:

  • +1 用于将new 与无保护的性行为进行比较,这有助于解释为什么 C++ 聊天室倾向于经常讨论。
  • +1 表示第一段。其余的我没有读。这是一篇好文章吗?
  • 这似乎是一个经过深思熟虑和完整的解释。谢谢你的澄清
  • “从另一种 OO 语言转到 C++ 时要意识到的最重要的事情是,大多数对象只需要具有自动生命周期,因此无需担心任何事情。”我同意“最”。如果您正在编写库或引擎 - 我会想象一个延长对象寿命的好用例。有人可能会争辩说你可以只返回一个对象的副本——但我真的认为这是一种浪费。
  • C++ 是一种用于编写库的语言”对我来说有点奇怪。这可能非常适合,但这并不意味着它适合。当然,在我看来,C++ 的起源故事并没有考虑到这个目的,而且我以前也没有听过这个具体的说法。你能详细说明一下你的意思吗?
【解决方案2】:

尽可能避免new,意味着很多好处,例如:

  • 首先,您还要避​​免使用delete 语句。尽管智能指针在这里可以为您提供帮助。所以这不是强项。

  • 您避免使用三法则(在 C++03 中)或五法则(在 C++11 中)。如果您在设计类时使用new,即当您的类在内部管理原始内存时,您可能必须考虑此规则。

  • 当您不使用new 时,很容易实现异常安全代码。否则,您将面临很多问题,使您的代码异常安全。

不必要地使用new 意味着您正在引发问题。我见过一个没有经验的程序员使用new 时,他通常有更好的选择,例如使用标准容器和算法。使用标准容器可以避免显式使用 new 带来的大多数问题。

【讨论】:

  • 设计一个类时必须考虑的三/五法则,而不是在决定如何使用它时。不过,其他两个是正确的。
  • @sbi:我知道,这就是为什么我说“......你可能必须考虑这条规则。”。跨度>
  • @sbi:我现在已经在回答中说得很清楚了。
  • 我想现在我明白了:你的意思是当你不new 类数据成员时??
  • 关于 TRoT/F 的那个。当我设计一个类时,我需要决定实现复制/移动,无论类的对象是动态创建还是自动创建。如果您不是指设计的类的用法,而是指其中的数据成员是否动态创建,那么您是对的。只是阅读您的答案并不清楚。
【解决方案3】:

这还不错,但是您使用new 分配的所有内容都必须使用delete 释放。这并不总是微不足道的,尤其是当您考虑到异常时。 我想这就是那个帖子的意思。

【讨论】:

  • 您使用new 分配的所有内容都必须使用delete 释放 - 是的,但智能指针会在大多数情况下自动为您执行此操作案例。
【解决方案4】:

这不是“使用 new 的坏主意”——发帖人错误地陈述了他的情况。相反,使用 new 而不是给你两个不同的东西。

new 为您提供一个新的、单独分配的类实例,并返回一个指向该实例的指针。

使用不带new 的类名会创建该类的一个自动实例,当它超出范围时会“噗嗤”一声。这会“返回”实例本身,而不是指针(因此其他线程中的语法错误)。

如果在引用的情况下使用new 并添加* 以传递编译器,则会导致对象泄漏。另一方面,如果您将参数传递给将其存储在某处的方法,并且您传递了一个非新实例,使其与&amp; 一起工作,那么您最终会存储一个悬空指针.

【讨论】:

  • 其实我不认为 Jerry 说错了,但我认为 wtfsven 理解得很差。这是对特定问题的答案,而且答案是正确的,IMNSHO。我同意,一般来说,您应该尽可能避免使用new/delete
  • 我的意思是他没有解释为什么会这样——他基本上只是说“如果可以避免,就不要使用 new”,这甚至没有开始解释差异。
  • @Daniel 但是解释普遍性是相当重要的,并不真正属于另一个答案。它属于这里。 ;-)
  • 那你为什么不发表解释呢? ;)
【解决方案5】:

newdeletefreemalloc (不要混合它们,你会遇到麻烦)。有时您必须使用 new,因为使用 new 分配的数据不会超出范围......除非数据指针丢失,这是new 的全部问题。 但这是程序员的错误,而不是关键字。

【讨论】:

  • 不要在 C++ 中使用 malloc。
  • @CatPlusPlus: ...除非你想要或需要:-)(看着malloc-allocator。)
  • "有时你必须使用 new,因为用 new 分配的数据不会超出范围......" 通常有更好的方法来解决这个问题,比如使用std::shared_ptr 或另一个资源管理、所有权共享类,它们仍然处理所有丑陋的细节,如 new/delete 并避免用户必须把它们弄好(从统计上讲,不可避免地会忘记这样做) .
【解决方案6】:

这取决于代码需要什么。在您引用的回复中,向量包含客户端实例,而不是指向客户端实例的指针。

在C++中,可以直接在栈上创建对象,不用new,像下面代码中的V1和V2:

void someFct()
{
     std::vector<client> V1;
     //....
     std::vector<client*> V2;
}

使用 V2 时,您必须使用 new 操作创建新的客户端实例,但是当 V2 超出范围时,客户端对象不会被释放(删除)。没有垃圾收集器。您必须在离开函数之前删除对象。

要自动删除创建的实例,您可以使用 std::shared_ptr。这使代码编写时间更长,但从长远来看更易于维护:

void someFct()
{
    typedef std::shared_ptr<client> client_ptr;
    typedef std::vector<client_ptr> client_array;
    client_array V2;

    V2.push_back(client_ptr(new client()));

    // The client instance are now automatically released when the function ends, 
    // even if an exception is thrown.
}

【讨论】:

  • 展示基于指针的方法有什么意义,抱怨它们不会在函数末尾销毁对象,然后使用shared_ptr 将其添加回来?你为什么不像原来的V1 那样按值存储对象呢?如果您不需要它们超过当前范围,那么将它们通过动态分配是没有意义的,而这不需要。
【解决方案7】:

在 C++ 中实例化类时使用“new”真的是个坏主意吗?

这通常很糟糕,因为它不是必需的,而且当您不虚假地使用它时,代码会变得更容易。如果您可以在不使用它的情况下逃脱,请这样做。我用new写过整个库。

我知道使用原始指针是不明智的,但是当它是如此糟糕的做法时,为什么还要使用'new'关键字呢?还是这样?

这并不是普遍不好,只是大部分时间都是不必要的。但也有合适的时候,这就是为什么会有这样一个关键字。也就是说,C++ 可以在没有关键字的情况下摆脱困境,因为new 混淆了两个概念:1.它分配内存,2.它初始化 em> 对象的内存。

您可以使用其他内存分配方式解耦这些进程,然后调用构造函数(“placement new”)。这实际上是通过分配器在所有地方完成的,例如标准库。

另一方面,客户端代码管理未初始化的内存很少(阅读:从不)有意义,因此不解耦这两个进程是有意义的。因此存在new

【讨论】:

  • 消除关键字(a la Objective-C)并不能消除概念。
  • @Daniel 是的,我的回答也是如此。
猜你喜欢
  • 2020-08-10
  • 2014-08-23
  • 1970-01-01
  • 1970-01-01
  • 2017-01-31
  • 1970-01-01
  • 2019-04-07
  • 2013-10-02
  • 2015-05-12
相关资源
最近更新 更多