ChatGPT解决这个技术问题 Extra ChatGPT

为什么 C++ 程序员应该尽量减少“新”的使用?

我偶然发现 Stack Overflow 问题 Memory leak with std::string when using std::list<std::string>one of the comments 是这样说的:

停止使用新的这么多。我看不出你在任何地方使用 new 的任何原因。您可以在 C++ 中按值创建对象,这是使用该语言的巨大优势之一。您不必在堆上分配所有内容。不要像 Java 程序员那样思考。

我不太确定他的意思是什么。

为什么要在 C++ 中尽可能频繁地按值创建对象,它在内部有什么不同?我误解了答案吗?

这个问题的现代访问者应该注意到,较新的 C++ 标准定义了比 new 和裸指针更安全的动态分配新方法。如果今天问这个问题,答案可能会有所不同。关于动态分配的讨论通常是不必要的,仍然是相关的。但是,大多数答案早于智能指针。

C
Community

有两种广泛使用的内存分配技术:自动分配和动态分配。通常,每个都有一个相应的内存区域:堆栈和堆。

堆栈总是按顺序分配内存。它可以这样做是因为它要求您以相反的顺序(先进后出:FILO)释放内存。这是许多编程语言中局部变量的内存分配技术。它非常非常快,因为它需要最少的簿记并且要分配的下一个地址是隐式的。

在 C++ 中,这称为 自动存储,因为存储在作用域结束时会自动声明。当前代码块(使用 {} 分隔)的执行完成后,将自动收集该块中所有变量的内存。这也是调用 析构函数 来清理资源的时刻。

堆允许更灵活的内存分配模式。簿记更复杂,分配更慢。由于没有隐式释放点,您必须使用 deletedelete[](C 中的 free)手动释放内存。然而,没有隐式释放点是堆灵活性的关键。

使用动态分配的原因

即使使用堆的速度较慢并且可能导致内存泄漏或内存碎片,动态分配也有非常好的用例,因为它的限制较少。

使用动态分配的两个主要原因:

您不知道编译时需要多少内存。例如,在将文本文件读入字符串时,您通常不知道文件的大小,因此在运行程序之前您无法决定分配多少内存。

您想要分配在离开当前块后将持续存在的内存。例如,您可能想要编写一个函数 string readfile(string path) 来返回文件的内容。在这种情况下,即使堆栈可以保存整个文件内容,您也无法从函数返回并保留分配的内存块。

为什么动态分配通常是不必要的

在 C++ 中有一个简洁的构造,称为 析构函数。此机制允许您通过将资源的生命周期与变量的生命周期对齐来管理资源。这种技术被称为 RAII 并且是 C++ 的区别点。它将资源“包装”到对象中。 std::string 就是一个很好的例子。这个片段:

int main ( int argc, char* argv[] )
{
    std::string program(argv[0]);
}

实际上分配了可变数量的内存。 std::string 对象使用堆分配内存并在其析构函数中释放它。在这种情况下,您确实不需要需要手动管理任何资源,并且仍然可以获得动态内存分配的好处。

特别是,这意味着在此代码段中:

int main ( int argc, char* argv[] )
{
    std::string * program = new std::string(argv[0]);  // Bad!
    delete program;
}

有不需要的动态内存分配。该程序需要更多的输入(!)并引入了忘记释放内存的风险。它这样做没有明显的好处。

为什么应该尽可能频繁地使用自动存储

基本上,最后一段总结了它。尽可能频繁地使用自动存储使您的程序:

打字速度更快;

运行时更快;

不太容易发生内存/资源泄漏。

奖励积分

在引用的问题中,还有其他问题。特别是以下类:

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("foo_bar");
}

Line::~Line() {
    delete mString;
}

实际上使用起来比下面的风险大得多:

class Line {
public:
    Line();
    std::string mString;
};

Line::Line() {
    mString = "foo_bar";
    // note: there is a cleaner way to write this.
}

原因是 std::string 正确定义了复制构造函数。考虑以下程序:

int main ()
{
    Line l1;
    Line l2 = l1;
}

使用原始版本,该程序可能会崩溃,因为它在同一字符串上使用了两次 delete。使用修改后的版本,每个 Line 实例都将拥有自己的字符串 instance,每个实例都有自己的内存,并且都将在程序结束时释放。

其他注意事项

由于上述所有原因,广泛使用 RAII 被认为是 C++ 中的最佳实践。但是,还有一个不是立即显而易见的额外好处。基本上,它比各个部分的总和要好。整个机制组成。它可以扩展。

如果您使用 Line 类作为构建块:

 class Table
 {
      Line borders[4];
 };

然后

 int main ()
 {
     Table table;
 }

分配四个 std::string 实例、四个 Line 实例、一个 Table 实例和所有字符串的内容,自动释放所有内容


+1 最后提到 RAII,但应该有一些关于异常和堆栈展开的内容。
@Tobu:是的,但是这篇文章已经很长了,我想让它更专注于 OP 的问题。我最终会写一篇博客文章或其他内容,并从这里链接到它。
提到堆栈分配的 缺点 将是一个很好的补充(至少在 C++1x 之前)——如果你不小心,你经常需要不必要地复制东西。例如,当 Monster 死亡时,它会向 World 吐出 Treasure。在它的 Die() 方法中,它将宝藏添加到世界中。它必须使用 world->Add(new Treasure(/*...*/)) 来保存它死后的宝藏。替代方案是 shared_ptr(可能是过度杀伤)、auto_ptr(所有权转移的语义不佳)、按值传递(浪费)和 move + unique_ptr(尚未广泛实施)。
你所说的关于堆栈分配的局部变量可能有点误导。 “堆栈”是指调用堆栈,它存储堆栈帧。正是这些堆栈帧以 LIFO 方式存储。分配特定框架的局部变量,就好像它们是结构的成员一样。
@someguy:确实,解释并不完美。实施在其分配策略中具有自由度。但是,变量需要以 LIFO 方式初始化和销毁,所以类比成立。我认为这不会使答案进一步复杂化。
e
einpoklum

因为堆栈更快且防泄漏

在 C++ 中,只需要一条指令即可为给定函数中的每个局部范围对象分配空间(在堆栈上),并且不可能泄漏任何内存。该评论打算(或应该打算)说“使用堆栈而不是堆”之类的东西。


“只需要一条指令来分配空间”——哦,胡说八道。当然,添加到堆栈指针只需要一条指令,但是如果该类有任何有趣的内部结构,那么除了添加到堆栈指针之外,还有更多的事情要做。说在 Java 中分配空间不需要指令同样有效,因为编译器将在编译时管理引用。
@查理是正确的。自动变量快速且万无一失会更准确。
@Charlie:无论哪种方式都需要设置类内部。正在对分配所需空间进行比较。
咳嗽 int x; return &x;
快是的。但肯定不是万无一失的。没有什么是万无一失的。你可以得到一个 StackOverflow :)
N
Nicol Bolas

原因很复杂。

首先,C++ 不是垃圾收集器。因此,对于每一个新的,都必须有一个相应的删除。如果你没有把这个删除,那么你有内存泄漏。现在,对于这样一个简单的案例:

std::string *someString = new std::string(...);
//Do stuff
delete someString;

这很简单。但是如果“Do stuff”抛出异常会发生什么?糟糕:内存泄漏。如果“Do stuff”提前发出 return 会发生什么?糟糕:内存泄漏。

这是最简单的情况。如果您碰巧将该字符串返回给某人,现在他们必须删除它。如果他们将它作为参数传递,接收它的人是否需要删除它?他们什么时候应该删除它?

或者,您可以这样做:

std::string someString(...);
//Do stuff

没有delete。该对象是在“堆栈”上创建的,一旦超出范围就会被销毁。您甚至可以返回对象,从而将其内容传输到调用函数。您可以将对象传递给函数(通常作为引用或 const-reference: void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)。等等。

全部没有 newdelete。毫无疑问,谁拥有内存或谁负责删除它。如果你这样做:

std::string someString(...);
std::string otherString;
otherString = someString;

据了解,otherString 拥有 someString数据的副本。它不是指针;它是一个单独的对象。它们可能恰好具有相同的内容,但您可以更改其中一个而不影响另一个:

someString += "More text.";
if(otherString == someString) { /*Will never get here */ }

看到这个想法了吗?


关于这一点...如果一个对象是在 main() 中动态分配的,在程序期间存在,由于这种情况,不能轻易在堆栈上创建,并且指向它的指针被传递给任何需要的函数访问它,这会在程序崩溃的情况下导致泄漏,还是安全?我会假设后者,因为释放所有程序内存的操作系统也应该在逻辑上释放它,但我不想在 new 方面做任何假设。
@JustinTime您无需担心释放动态分配的对象的内存,这些对象将在程序的生命周期内保留。当程序执行时,操作系统会为它创建一个物理内存或虚拟内存的图集。虚拟内存空间中的每个地址都映射到物理内存的地址,当程序退出时,所有映射到它的虚拟内存的东西都会被释放。因此,只要程序完全退出,您就不必担心分配的内存永远不会被删除。
v
val is still with Monica

new 创建的对象必须最终delete d,以免它们泄漏。析构函数不会被调用,内存不会被释放,整个位。由于 C++ 没有垃圾收集,所以这是一个问题。

按值创建的对象(即在堆栈上)在超出范围时会自动死亡。析构函数调用由编译器插入,内存在函数返回时自动释放。

unique_ptrshared_ptr 这样的智能指针解决了悬空引用问题,但它们需要编码纪律并且存在其他潜在问题(可复制性、引用循环等)。

此外,在多线程场景中,new 是线程之间的争用点;过度使用 new 可能会影响性能。根据定义,堆栈对象的创建是线程本地的,因为每个线程都有自己的堆栈。

值对象的缺点是一旦宿主函数返回它们就会死掉——你不能将引用传回给调用者,只能通过复制、返回或按值移动。


+1。关于“new 创建的对象必须最终delete以免泄漏。” - 更糟糕的是,new[] 必须与 delete[] 匹配,如果您使用 delete new[]-ed 内存或 delete[] new-ed 内存,您会得到未定义的行为 - 很少有编译器会警告这一点(一些像 Cppcheck 这样的工具可以做到)。
@TonyDelroy 在某些情况下编译器无法发出警告。如果一个函数返回一个指针,它可以被创建为 new(单个元素)或 new[]。
e
einpoklum

C++ 本身不使用任何内存管理器。其他语言如 C#、Java 有垃圾收集器来处理内存

C++ 实现通常使用操作系统例程来分配内存,过多的新/删除可能会使可用内存碎片化

对于任何应用程序,如果经常使用内存,建议预先分配它并在不需要时释放它。

不正确的内存管理可能会导致内存泄漏,而且很难跟踪。所以在函数范围内使用栈对象是一种行之有效的技术

使用堆栈对象的缺点是,它会在返回、传递给函数等时创建多个对象副本。然而,智能编译器很清楚这些情况,并且已经针对性能进行了很好的优化

如果在两个不同的地方分配和释放内存,在 C++ 中真的很乏味。释放的责任始终是一个问题,我们主要依赖于一些常用的可访问指针、堆栈对象(最大可能)和 auto_ptr(RAII 对象)等技术

最好的事情是,您可以控制内存,最糟糕的是,如果我们对应用程序使用不正确的内存管理,您将无法控制内存。由于内存损坏导致的崩溃是最严重且难以追踪的。


实际上,任何分配内存的语言都有一个内存管理器,包括 c.大多数都非常简单,即 int *x = malloc(4); int *y = malloc(4); ...第一次调用将分配内存,也就是向 os 请求内存,(通常以 1k/4k 块的形式),因此第二次调用实际上不会分配内存,而是为您提供它分配的最后一块。 IMO,垃圾收集器不是内存管理器,因为它只处理内存的自动释放。要被称为内存管理器,它不仅应该处理释放,还应该处理内存分配。
局部变量使用堆栈,因此编译器不会调用 malloc() 或其朋友来分配所需的内存。但是,堆栈不能释放堆栈中的任何项目,释放堆栈内存的唯一方法是从堆栈顶部展开。
C++ 不“使用操作系统例程”;这不是语言的一部分,它只是一个常见的实现。 C++ 甚至可以在没有任何操作系统的情况下运行。
E
Emily L.

我发现遗漏了一些尽可能少做新事物的重要原因:

运算符 new 具有不确定的执行时间

调用 new 可能会也可能不会导致操作系统为您的进程分配一个新的物理页面,如果您经常这样做,这可能会非常慢。或者它可能已经准备好合适的内存位置,我们不知道。如果您的程序需要具有一致且可预测的执行时间(例如在实时系统或游戏/物理模拟中),您需要在时间关键循环中避免 new

运算符 new 是隐式线程同步

是的,你听到了,你的操作系统需要确保你的页表是一致的,因此调用 new 会导致你的线程获取一个隐式互斥锁。如果您一直在从许多线程调用 new,那么您实际上是在序列化您的线程(我已经使用 32 个 CPU 完成了此操作,每个 CPU 都在 new 上获得几百个字节,哎呀!这是一个要调试的皇家皮塔)

其他答案已经提到了其他答案,例如速度慢、碎片化、容易出错等。


两者都可以通过使用placement new/delete和事先分配内存来避免。或者您可以自己分配/释放内存,然后调用构造函数/析构函数。这是 std::vector 通常的工作方式。
@rxantos 请阅读 OP,这个问题是关于避免不必要的内存分配。此外,没有展示位置删除。
使用堆栈在执行时间上也不是确定性的。除非您调用了 mlock() 或类似的东西。这是因为系统可能内存不足,并且没有可用于堆栈的现成物理内存页面,因此操作系统可能需要交换或将一些缓存(清除脏内存)写入磁盘,然后才能继续执行。
@mikkorantalainen 这在技术上是正确的,但是在内存不足的情况下,所有的赌注无论如何都会关闭,因为你正在推送到磁盘,所以你无能为力。无论如何,在合理的情况下,它不会使避免新调用的建议无效。
@einpoklum 我看到您似乎对这个问题的许多答案提出了质疑,这些答案在大多数(如果不是全部)实现中通常是正确的,但在技术上并没有在标准中强制要求以这种方式完成。甚至声称应该删除指出合法的主题问题的答案。理解和解决最常见的实施策略的怪癖是有价值的,即使它们在技术上不是按照标准要求这样做的,因为否则你的应用程序会受到影响......
C
Community

C++17 之前:

因为即使您将结果包装在智能指针中,它也容易发生微妙的泄漏。

考虑一个“细心”的用户,他记得将对象包装在智能指针中:

foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));

此代码很危险,因为无法保证 shared_ptr 是在 T1T2 之前构造的。因此,如果 new T1()new T2() 之一在另一个成功后失败,则第一个对象将被泄漏,因为不存在 shared_ptr 来销毁和释放它。

解决方案:使用make_shared

后 C++17:

这不再是一个问题:C++17 对这些操作的顺序施加了约束,在这种情况下,确保每次调用 new() 之后必须立即构造相应的智能指针,没有之间的其他操作。这意味着,在调用第二个 new() 时,可以保证第一个对象已经被包装在其智能指针中,从而防止在引发异常时发生任何泄漏。

Barry in another answer 提供了对 C++17 引入的新评估顺序的更详细说明。

感谢 @Remy Lebeau 指出这仍然在 C++17 下是一个问题(尽管不太严重):shared_ptr 构造函数可能无法分配其控制块并抛出,在这种情况下传递给它的指针不会被删除。

解决方案:使用make_shared


其他解决方案:永远不要为每行动态分配一个以上的对象。
@Antimony:是的,与没有分配任何对象相比,当您已经分配了一个对象时,分配多个对象更具诱惑力。
我认为更好的答案是,如果调用异常并且没有任何东西捕获它, smart_ptr 会泄漏。
即使在 C++17 后的情况下,如果 new 成功并且随后的 shared_ptr 构造失败,仍然可能发生泄漏。 std::make_shared() 也可以解决这个问题
@Mehrdad 有问题的 shared_ptr 构造函数为存储共享指针和删除器的控制块分配内存,所以是的,理论上它可以引发内存错误。只有复制、移动和别名构造函数是不抛出的。 make_shared 在控制块本身内部分配共享对象,因此只有 1 个分配而不是 2 个。
A
Andrew Edgecombe

在很大程度上,这是某人将自己的弱点提升为一般规则。使用 new 运算符创建对象本身并没有错。有一些争论是你必须遵守一些纪律:如果你创建一个对象,你需要确保它会被销毁。

最简单的方法是在自动存储中创建对象,因此 C++ 知道在超出范围时将其销毁:

 {
    File foo = File("foo.dat");

    // do things

 }

现在,请注意,当您在结束大括号之后从该块上掉下来时,foo 超出了范围。 C++ 会自动为你调用它的 dtor。与 Java 不同,您不需要等待 GC 找到它。

你写了吗

 {
     File * foo = new File("foo.dat");

你会想明确地匹配它

     delete foo;
  }

甚至更好的是,将您的 File * 分配为“智能指针”。如果你不小心,它可能会导致泄漏。

答案本身做出了错误的假设,即如果您不使用 new 您就不会在堆上分配;事实上,在 C++ 中你并不知道这一点。最多,你知道一小部分内存,比如一个指针,肯定是在堆栈上分配的。但是,请考虑 File 的实现是否类似于

  class File {
    private:
      FileImpl * fd;
    public:
      File(String fn){ fd = new FileImpl(fn);}

那么 FileImpl仍然在堆栈上分配。

是的,你最好确保有

     ~File(){ delete fd ; }

在课堂上也是如此;没有它,即使您根本没有在堆上分配内存,您也会从堆中泄漏内存。


您应该查看引用问题中的代码。该代码中肯定有很多问题。
我同意使用 new 本身 没有任何问题,但是如果您查看评论所引用的原始代码,就会发现 new 被滥用了。代码像 Java 或 C# 一样编写,其中 new 用于几乎每个变量,而堆栈上的东西更有意义。
有道理。但是通常会强制执行一般规则以避免常见的陷阱。无论这是否是个人的弱点,内存管理都足够复杂,足以保证这样的一般规则! :)
@Charlie:评论确实说你永远不应该使用new。它说如果您在动态分配和自动存储之间进行选择,请使用自动存储。
@Charlie:使用 new 没有任何问题,但如果使用 delete,你就错了!
A
Andrew Edgecombe

new() 不应尽可能little 使用。应该尽可能小心使用它。并且应该根据实用主义的要求尽可能频繁地使用它。

在堆栈上分配对象,依赖于它们的隐式销毁,是一个简单的模型。如果对象的所需范围适合该模型,则无需使用 new(),以及关联的 delete() 并检查 NULL 指针。在堆栈上有大量短期对象分配的情况下,应该减少堆碎片的问题。

但是,如果您的对象的生命周期需要超出当前范围,那么 new() 是正确的答案。只需确保您注意何时以及如何调用 delete() 以及 NULL 指针的可能性,使用已删除的对象以及使用指针带来的所有其他问题。


“如果您的对象的生命周期需要超出当前范围,那么 new() 是正确的答案”......为什么不优先按值返回或通过非const ref 或指针接受调用者范围的变量.. .?
@Tony:是的,是的!我很高兴听到有人提倡参考。创建它们是为了防止这个问题。
@TonyD ...或组合它们:按值返回智能指针。这样,调用者和在许多情况下(即 make_shared/_unique 可用的情况下)被调用者永远不需要 newdelete。这个答案错过了真正的要点:(A) C++ 提供了诸如 RVO、移动语义和输出参数之类的东西——这通常意味着通过返回动态分配的内存来处理对象创建和生命周期扩展变得不必要且粗心。 (B) 即使在需要动态分配的情况下,stdlib 也提供了 RAII 包装器,可以让用户摆脱丑陋的内部细节。
T
Tim

当你使用 new 时,对象被分配到堆中。它通常在您预期扩展时使用。当您声明一个对象时,例如,

Class var;

它被放置在堆栈上。

您将始终必须使用 new 对放置在堆上的对象调用destroy。这打开了内存泄漏的可能性。放在堆栈上的对象不容易发生内存泄漏!


+1“[堆]通常在您预期扩展时使用”-例如附加到 std::stringstd::map,是的,敏锐的洞察力。我最初的反应是“但也很常见地将对象的生命周期与创建代码的范围解耦”,但真正按值返回或通过非 const 引用或指针接受调用者范围的值更好,除非有“扩张”也涉及。还有一些其他的声音用途,比如工厂方法......
t
tylerl

避免过度使用堆的一个显着原因是为了提高性能——特别是涉及 C++ 使用的默认内存管理机制的性能。虽然在普通情况下分配可能很快,但在没有严格顺序的情况下对大小不均匀的对象执行大量 newdelete 不仅会导致内存碎片,而且还会使分配算法复杂化,并且绝对会破坏在某些情况下的表现。

这就是创建 memory pools 来解决的问题,它可以减轻传统堆实现的固有缺点,同时仍允许您根据需要使用堆。

不过,最好还是完全避免这个问题。如果你可以把它放在堆栈上,那么就这样做。


如果速度有问题,您总是可以分配相当大的内存量,然后使用放置新/删除。
内存池是为了避免碎片,加速释放(一个释放数千个对象)并使释放更安全。
K
Khaled Nassar

我认为张贴者的意思是 You do not have to allocate everything on theheap 而不是 stack

基本上,对象是在堆栈上分配的(当然,如果对象大小允许的话),因为堆栈分配的成本很低,而不是基于堆的分配,这涉及分配器的相当多的工作,并且增加了冗长,因为你必须管理在堆上分配的数据。


T
Thomas Berger

我倾向于不同意使用新的“太多”的想法。尽管原始海报将 new 与系统类一起使用有点荒谬。 (int *i; i = new int[9999];?真的吗?int i[9999]; 更清晰。)我认为 是引起评论者注意的原因。

当您使用系统对象时,很少需要对完全相同的对象进行多次引用。只要价值相同,那才是最重要的。并且系统对象通常不会占用太多内存空间。 (每个字符一个字节,在一个字符串中)。如果他们这样做了,库的设计应该考虑到内存管理(如果它们写得很好)。在这些情况下,(除了他的代码中的一两个新闻),new 实际上毫无意义,只会带来混乱和潜在的错误。

但是,当您使用自己的类/对象(例如原始海报的 Line 类)时,您必须自己开始考虑内存占用、数据持久性等问题。在这一点上,允许对同一个值的多次引用是无价的——它允许像链表、字典和图形这样的结构,其中多个变量不仅需要具有相同的值,而且还需要引用完全相同的对象 在内存中。但是, Line 类没有任何这些要求。所以原始发帖人的代码实际上完全不需要new


当您事先不知道数组的大小时,通常会使用 new/delete。当然 std::vector 会为您隐藏新/删除。你仍然使用它们,但是通过 std::vector。所以现在它会在你不知道数组的大小并且由于某种原因想要避免 std::vector 的开销时使用(它很小,但仍然存在)。
When you're working with your own classes/objects ...您通常没有理由这样做!一小部分问题是关于熟练编码人员的容器设计细节。与此形成鲜明对比的是,令人沮丧的比例是关于不知道标准库存在的新手的困惑 - 或者在“编程”“课程”中积极分配糟糕的任务,导师要求他们毫无意义地重新发明轮子 - 在他们了解轮子是什么以及为什么它起作用之前。通过促进更抽象的分配,C++ 可以将我们从 C 无休止的“链表段错误”中解救出来;拜托,让我们让它
原发帖人将 new 与系统类一起使用有点荒谬。(int *i; i = new int[9999];?真的吗?int i[9999]; 更清晰。)” 是的,它更清晰,但要扮演魔鬼的拥护者,类型不一定是一个坏论点。对于 9999 个元素,我可以想象一个紧凑的嵌入式系统没有足够的堆栈来容纳 9999 个元素:9999x4 字节约为 40 kB,x8 ~80 kB。因此,此类系统可能需要使用动态分配,假设它们使用替代内存来实现它。尽管如此,这只能证明动态分配是合理的,而不是new;在这种情况下,vector 将是真正的解决方法
同意@underscore_d - 这不是一个很好的例子。我不会像那样在我的堆栈中添加 40,000 或 80,000 字节。我实际上可能会将它们分配在堆上(当然是 std::make_unique<int[]>())。
P
Peter Mortensen

两个原因:

在这种情况下是不必要的。您正在使您的代码不必要地变得更加复杂。它在堆上分配空间,这意味着你以后要记得删除它,否则会导致内存泄漏。


e
einpoklum

许多答案都涉及各种性能考虑。我想解决令 OP 感到困惑的评论:

不要像 Java 程序员那样思考。

实际上,在 Java 中,如 this question 的答案中所解释的,

首次显式创建对象时使用 new 关键字。

但在 C++ 中,T 类型的对象是这样创建的:T{}(或 T{ctor_argument1,ctor_arg2} 用于带参数的构造函数)。这就是为什么您通常没有理由要使用 new

那么,为什么它一直被使用呢?嗯,有两个原因:

您需要创建许多值,其数量在编译时是未知的。由于通用机器上 C++ 实现的限制 - 通过分配太多空间以常规方式创建值来防止堆栈溢出。

现在,除了您引用的评论所暗示的内容之外,您应该注意,即使上面的这两种情况也得到了很好的覆盖,而您不必自己“求助”使用 new

您可以使用标准库中的容器类型,这些容器类型可以容纳运行时可变数量的元素(如 std::vector)。

您可以使用智能指针,它为您提供类似于 new 的指针,但要确保在“指针”超出范围时释放内存。

因此,避免显式 newdeleteGuideline R.11 是 C++ 社区编码指南中的官方项目。


P
Peter Mortensen

核心原因是堆上的对象总是比简单的值更难使用和管理。编写易于阅读和维护的代码始终是任何认真的程序员的首要任务。

另一种情况是我们使用的库提供了值语义并且不需要动态分配。 Std::string 就是一个很好的例子。

然而,对于面向对象的代码,使用指针——这意味着使用 new 预先创建它——是必须的。为了简化资源管理的复杂性,我们有几十种工具让它尽可能简单,比如智能指针。基于对象的范式或通用范式假定价值语义并且需要更少或不需要new,正如其他地方的海报所述。

传统的设计模式,尤其是 GoF 书中提到的那些,大量使用 new,因为它们是典型的 OO 代码。


这是一个糟糕的答案。 For object oriented code, using a pointer [...] is a must废话。如果您仅通过引用一个小子集来贬低“OO”,那么 polymorphism - also 废话:引用也可以。 [pointer] means use new to create it beforehand尤其是废话:可以将引用或指针用于自动分配的对象和多态使用; 看着我[typical OO code] use new a lot:也许在一些旧书中,但谁在乎?任何模糊的现代 C++ 都尽可能避开 new/raw 指针 - & 绝不这样做会减少 OO
k
klutt

new 是新的 goto

回想一下为什么 goto 如此受辱:虽然它是一种功能强大的低级流控制工具,但人们经常以不必要的复杂方式使用它,从而使代码难以遵循。此外,最有用和最容易阅读的模式被编码在结构化编程语句中(例如 forwhile);最终的结果是 goto 是适当方式的代码相当少见,如果你想写 goto,你可能做的很糟糕(除非你真的知道什么你正在做的)。

new 类似 - 它通常用于使事情变得不必要的复杂和难以阅读,并且可以将最有用的使用模式编码为各种类。此外,如果您需要使用任何还没有标准类的新使用模式,您可以编写自己的类来对它们进行编码!

我什至会争辩说,newgoto更糟糕,因为需要将 newdelete 语句配对。

goto 一样,如果您认为需要使用 new,那么您可能做错了 — 尤其是如果您在一个类的实现之外这样做,该类的生活目的是封装您需要的任何动态分配做。


我会补充说:“你基本上不需要它”。
举一个可以用来代替 new 的构造示例可能会有所帮助。
“能力越大,责任越大” 这不是愚蠢到用教条来崇拜有用的语言特征的问题。最好对风险进行教育,让人们犯下他们需要犯的任何错误,以正确理解问题。说“不要去那里”所做的只是让一些人故意去那里没有正确的想法或胆怯的避开它并永远生活在无知中。
M
Michael Chourdakis

以上所有正确答案的另一点,这取决于您正在执行哪种编程。例如,在 Windows 中开发内核 -> 堆栈受到严重限制,您可能无法像在用户模式下那样处理页面错误。

在这样的环境中,新的或类似 C 的 API 调用是首选,甚至是必需的。

当然,这只是规则的一个例外。


r
robert

new 在堆上分配对象。否则,将在堆栈上分配对象。查找 the difference between the two


我确信提问者知道其中的区别(尽管它并不是那么简单:例如创建一个 std::vector 使用堆栈和堆内存)。您尚未回答实际提出的问题:为什么我们希望尽量减少 new 的使用。