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


873

在使用std :: list <std :: string>时偶然发现了std :: string的内存溢出问题内存泄漏其中一条评论说:

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

我不确定他的意思。

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

Answers:


1037

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

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

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

堆允许更灵活的内存分配模式。簿记更加复杂,分配也较慢。因为没有隐式释放点,所以必须使用C中的deletedelete[]free)手动释放内存。但是,缺少隐式释放点是堆灵活性的关键。

使用动态分配的原因

即使使用堆的速度较慢,并且可能导致内存泄漏或内存碎片,但由于动态内存分配的局限性较小,因此存在很好的用例。

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

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

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

为什么通常不需要动态分配

在C ++中,有一个名为destructor的简洁构造。此机制使您可以通过将资源的生存期与变量的生存期对齐来管理资源。该技术称为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实例将拥有自己的字符串实例,每个实例都具有自己的内存,并且两者都将在程序结尾处释放。

其他注意事项

由于上述所有原因,广泛使用RAII被认为是C ++中的最佳实践。但是,还有其他好处尚不明显。基本上,它比各个部分的总和要好。整个机制组成。它可以缩放。

如果将Line类用作构建块:

 class Table
 {
      Line borders[4];
 };

然后

 int main ()
 {
     Table table;
 }

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


55
+1是最后提到RAII的地方,但应该有一些关于异常和堆栈展开的内容。
东武

7
@Tobu:是的,但是这篇文章已经很长了,我想让它专注于OP的问题。我最终将写一篇博客文章或其他内容,然后从此处链接到它。
安德烈·卡隆

15
提及堆栈分配的缺点(至少直到C ++ 1x为止)将是一个很好的补充-如果您不小心,则经常需要不必要地复制内容。例如,死时Monster吐出一个TreasureWorld。它的Die()方法将宝藏带给世界。world->Add(new Treasure(/*...*/))死亡后,必须将其用于保存珍宝。备选方案是shared_ptr(可能是过大的),auto_ptr(所有权转让的语义不佳),按值传递(浪费)和move+ unique_ptr(尚未广泛实施)。
kizzx2 2011年

7
您对堆栈分配的局部变量的说法可能会引起误解。“堆栈”是指调用堆栈,其中存储堆栈帧。这些堆栈框架以LIFO方式存储。分配特定框架的局部变量,就像它们是结构的成员一样。
someguy 2011年

7
@someguy:确实,这种解释并不完美。该实现在其分配策略中具有自由。但是,需要以LIFO方式初始化和销毁​​变量,因此可以类推。我认为这不会使答案进一步复杂化。
安德烈·卡隆

171

因为堆栈速度更快且防漏

在C ++中,只需要一条指令就可以为给定函数中的每个局部作用域对象在堆栈上分配空间,并且不可能泄漏任何该内存。该注释意图(或应该意图)说诸如“使用堆栈而不是堆”之类的内容。


18
“分配空间只需要一条指令” –废话。当然,只需要一条指令就可以添加到堆栈指针,但是如果类具有任何有趣的内部结构,那么添加到堆栈指针的内容将远远不止于此。同样可以说在Java中不需要分配空间的指令,因为编译器将在编译时管理引用。
查理·马丁

31
@查理是正确的。 自动变量是快速的,万无一失将更加准确。
奥利弗·查尔斯沃思

28
@Charlie:两种方法都需要设置类的内部。在分配所需空间方面进行了比较。
奥利弗·查尔斯沃思

51
咳嗽 int x; return &x;
peterchen 2011年

16
快是的。但是肯定不是万无一失的。没有什么是万无一失的。您可以获得StackOverflow :)
rxantos

107

原因很复杂。

首先,C ++不会被垃圾收集。因此,对于每个新项,必须有一个对应的删除项。如果您无法放入此删除项,则内存泄漏。现在,对于这样的简单情况:

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

这很简单。但是,如果“东西”抛出异常会怎样?糟糕:内存泄漏。如果“做东西” 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 */ }

看到这个主意了吗?


1
需要注意的是...如果main()在程序中将对象动态分配到中,则该对象在程序的存在期间一直存在,由于这种情况,无法在堆栈上轻松创建该对象,并且将其指针传递给需要访问该对象的任何函数,在程序崩溃的情况下会导致泄漏吗?还是安全?我会假设是后者,因为OS分配所有程序的内存也应该在逻辑上对其进行分配,但是在涉及到时,我不想做任何事情new
贾斯汀时间-恢复莫妮卡

4
@JustinTime您无需担心释放动态分配的对象的内存,这些对象将在程序的生命周期内保留。程序执行时,操作系统会为其创建物理内存或虚拟内存的地图集。虚拟内存空间中的每个地址都映射到物理内存的地址,并且当程序退出时,所有映射到其虚拟内存的内容都会被释放。因此,只要程序完全退出,您就不必担心分配的内存永远不会被删除。
艾曼·埃里亚尼

75

new最终创建的对象必须delete避免它们泄漏。整个过程都不​​会调用析构函数,也不会释放内存。由于C ++没有垃圾回收,因此这是一个问题。

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

智能指针一样unique_ptrshared_ptr解决了悬挂引用问题,但它们需要编码规则,并有其他潜在问题(可拷贝性,参考循环等)。

同样,在高度多线程的情况下,new线程之间的争用是重点。过度使用会对性能产生影响new。根据定义,堆栈对象的创建是线程局部的,因为每个线程都有自己的堆栈。

值对象的缺点是,一旦宿主函数返回,它们就会死掉-您不能仅通过复制,返回或按值移动将对它们的引用传递回调用方。


9
+1。关于“创建的对象new最终必须delete避免它们泄漏”。-更糟糕的是,new[]必须与匹配delete[],并且delete new[]-ed内存或delete[] new-ed内存会导致未定义的行为-很少有编译器对此发出警告(某些工具,如Cppcheck会在可能的情况下进行)。
Tony Delroy

3
@TonyDelroy在某些情况下,编译器无法发出警告。如果函数返回一个指针,则可以使用new(单个元素)或new []创建它。
fbafelipe 2012年

32
  • C ++自己没有使用任何内存管理器。其他语言,例如C#,Java具有垃圾收集器来处理内存
  • C ++实现通常使用操作系统例程来分配内存,太多的新操作/删除操作可能会使可用内存碎片化
  • 对于任何应用程序,如果经常使用内存,建议对其进行预分配并在不需要时释放。
  • 内存管理不当可能导致内存泄漏,而且很难跟踪。因此,在功能范围内使用堆栈对象是一种行之有效的技术
  • 使用堆栈对象的缺点是,它会在返回,传递给函数等时创建对象的多个副本。但是,精巧的编译器已充分意识到了这些情况,并且对其性能进行了优化。
  • 如果在两个不同的位置分配和释放内存,则在C ++中确实很繁琐。发布的责任始终是一个问题,并且大多数情况下,我们依赖于一些通常可访问的指针,堆栈对象(最大可能)和诸如auto_ptr的技术(RAII对象)
  • 最好的事情是,您已经控制了内存,最糟糕的事情是,如果我们对应用程序使用了不正确的内存管理,则您将无法控制内存。由于内存损坏而导致的崩溃是最令人讨厌且难以跟踪的。

5
实际上,任何分配内存的语言都有一个内存管理器,包括c。大多数都非常简单,即int * x = malloc(4); int * y = malloc(4); ...第一个调用将分配内存,也要向os询问内存(通常以1k / 4k块为单位),因此第二个调用实际上并没有分配内存,而是为您分配了最后一个分配给它的块。IMO,垃圾收集器不是内存管理器,因为它只能处理内存的自动释放。被称为内存管理器,它不仅应处理内存的分配,而且还应处理内存的分配。
拉赫利2015年

1
局部变量使用堆栈,因此编译器不会malloc()向其发出调用或与其朋友分配所需的内存。但是,堆栈无法释放堆栈中的任何项目,释放堆栈内存的唯一方法是从堆栈顶部展开。
Mikko Rantalainen '18 -10-12

C ++不“使用操作系统例程”;那不是语言的一部分,它只是一个常见的实现。C ++甚至可以在没有任何操作系统的情况下运行。
einpoklum

22

我发现错过了一些尽可能少的新事物的重要原因:

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

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

运算符new是隐式线程同步

是的,您听说过,您的操作系统需要确保页面表是一致的,因此调用new将导致您的线程获取隐式互斥锁。如果您一直new从多个线程进行调用,则实际上是在对线程进行序列化(我已经用32个CPU进行了此操作,每个CPU都new获得了数百个字节,哎呀!这是调试的皇家皮塔饼)

诸如慢,碎片,易于出错等之类的其他问题已经在其他答案中提及。


2
可以通过使用new / delete放置并事先分配内存来避免这两种情况。或者,您可以自己分配/释放内存,然后调用构造函数/析构函数。这就是std :: vector通常起作用的方式。
rxantos

1
@rxantos请阅读OP,这个问题是关于避免不必要的内存分配。另外,没有放置删除。
艾米丽·L.

@Emily,这就是OP的意思,我认为:void * someAddress = ...; delete (T*)someAddress
xryl669

1
使用堆栈在执行时间上也不是确定的。除非您打过电话mlock()或类似的电话。这是因为系统可能内存不足,并且堆栈没有可用的物理内存页面,因此操作系统可能需要在执行之前交换或向磁盘写入一些缓存(清除脏内存)。
Mikko Rantalainen '18 -10-12

1
@mikkorantalainen从技术上讲是正确的,但是在内存不足的情况下,当您将性能推送到磁盘时,所有赌注都会失去性能,因此您无能为力。在合理的情况下,避免新呼叫无论如何都不会使建议无效。
艾米丽·

21

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 在另一个答案中提供了C ++ 17引入的新评估顺序的更详细说明。

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

解决方案:使用make_shared


5
其他解决方案:永远不要动态分配每行多个对象。
锑2013年

3
@Antimony:是的,虽然您没有分配任何对象,但是分配多个对象的诱惑却更大。
user541686 2013年

1
我认为一个更好的答案是,如果调用了异常但没有任何异常捕获,smart_ptr将泄漏。
Natalie Adams

2
即使在后C ++ 17的情况下,如果new成功仍然会发生泄漏,然后后续shared_ptr构造也会失败。std::make_shared()也会解决这一问题
Remy Lebeau,

1
有问题的shared_ptr构造函数@Mehrdad 为存储共享指针和删除器的控制块分配内存,因此,是的,从理论上讲,它可以引发内存错误。仅复制,移动和别名构造函数是非抛出的。make_shared分配控制块本身内的共享对象,所以只有1分配而不是2
雷米勒博

17

在很大程度上,这是将自己的弱点提升为一般规则的人。使用运算符创建对象本身没有错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 ; }

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


4
您应该看一下所引用问题中的代码。该代码中肯定有很多错误。
安德烈·卡伦

7
我同意使用new 本身没有错,但是如果您查看注释所引用的原始代码,new则会被滥用。代码的编写就像是Java或C#,new实际上,将每个变量都放在堆栈上时,几乎所有变量都使用该代码。
卢克

5
有道理。但是通常会执行一般规则以避免常见的陷阱。无论这是否是个人弱点,内存管理的复杂性足以保证像这样的一般规则!:)
Robben_Ford_Fan_boy 2011年

9
@Charlie:注释并没有说你不应该使用new。它表示,如果您可以在动态分配和自动存储之间进行选择,请使用自动存储。
安德烈·卡隆

8
@Charlie:使用并没有错new,但是如果使用delete,那就错了!
Matthieu M.

16

new()不应该被用来作为越好。应该尽可能小心地使用它。而且,应根据实用主义的需要尽可能多地使用它。

依靠对象的隐式破坏,将对象分配到堆栈上是一个简单的模型。如果对象的要求范围适合该模型,则无需使用new(),关联delete()和检查NULL指针。如果您在堆栈上分配了许多短期对象,则应减少堆碎片的问题。

但是,如果对象的生存期需要扩展到当前范围之外,那new()是正确的答案。只要确保您注意调用的时间delete()和方式以及NULL指针的可能性,使用删除的对象以及使用指针附带的所有其他陷阱即可。


9
“如果对象的生命周期需要扩展到当前范围之外,那么new()是正确的答案”……为什么不优先按值返回或不通过非constref或指针接受调用者范围的变量……?
Tony Delroy

2
@Tony:是的,是的!我很高兴听到有人提倡参考。创建它们是为了防止出现此问题。
内森·奥斯曼

@TonyD ...或组合它们:按值返回智能指针。这样,调用方以及在许多情况下(即在make_shared/_unique可用的地方)被调用方就不需要new或不需要delete。这个答案错过了真正的要点:(A)C ++提供了RVO,移动语义和输出参数之类的东西-这通常意味着通过返回动态分配的内存来处理对象创建和生命周期扩展变得不必要和粗心。(B)即使在需要动态分配的情况下,stdlib也提供RAII包装器,以减轻用户的丑陋内部细节。
underscore_d

14

使用new时,对象将分配给堆。通常在预期扩展时使用。当您声明诸如

Class var;

它放在堆栈上。

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


2
+1“ [heap]通常在您预期扩展时使用”-就像附加到std::stringstd::map,是,敏锐的洞察力。我最初的反应是“但也通常将对象的生命周期与创建代码的作用域脱钩”,但实际上通过值返回或通过非const引用或指针接受调用方作用域的值会更好,除非涉及到“扩展”太。这里也有一些其他声音的用途就像工厂方法虽然....
托尼德尔罗伊

12

避免过度使用堆的一个显着原因是性能-特别是涉及C ++使用的默认内存管理机制的性能。虽然分配可以在琐碎的情况下是相当快的,做了很多的newdelete非均匀大小的物体没有严格的秩序,不仅导致内存碎片,但它也分配算法复杂化,并完全可以摧毁在某些情况下的性能。

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

不过,最好还是完全避免该问题。如果可以将其放在堆栈上,请这样做。


您总是可以分配相当大的内存,然后在速度有问题时使用new / delete放置。
rxantos

内存池应避免碎片化,以加快重新分配(针对数千个对象的一次重新分配),并使重新分配更加安全。
Lothar

10

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

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


10

我倾向于不同意使用新的“太多”的想法。尽管原始发布者在系统类中使用new有点荒谬。(int *i; i = new int[9999];真的吗?int i[9999];要清楚得多。)我认为就是引起评论者注意的地方。

当你与系统对象的工作,这是非常罕见的,你需要不止一个参考完全相同的对象。只要值相同,就很重要。系统对象通常不会占用太多内存。(每个字符一个字节,一个字符串)。如果这样做的话,库的设计应考虑到该内存管理(如果编写得当)。在这些情况下(除了代码中的一两个新闻,所有新闻都是毫无意义的),只会引起混乱和潜在的错误。

但是,当您使用自己的类/对象(例如,原始发布者的Line类)时,您必须开始自己考虑诸如内存占用,数据持久性等问题。在这一点上,允许多次引用相同的值是非常宝贵的-它允许使用链表,字典和图形之类的构造,其中多个变量不仅需要具有相同的值,而且还必须在内存中引用完全相同的对象。但是,Line类没有任何这些要求。因此,原始发布者的代码实际上绝对不需要new


通常,当您事先不知道数组的大小时,将使用new / delete。当然std :: vector会为您隐藏new / delete。您仍在使用它们,但是槽std :: vector。因此,如今,当您不知道数组的大小并且出于某种原因想要避免std :: vector的开销(虽然很小,但仍然存在)时,就可以使用它。
rxantos

When you're working with your own classes/objects...您通常没有理由这样做!Q的一小部分与熟练的编码人员有关容器设计的细节有关。与之形成鲜明对比的是,令人沮丧的比例不知道stdlib存在的新手的困惑-或在“编程”“课程”中积极地分配了艰巨的任务,导师要求他们毫无意义地重新发明轮子-在他们甚至了解了什么是轮子以及它为什么起作用。通过促进更多的抽象分配,C ++可以使我们摆脱C的无尽“带有链表的段错误”;拜托,让我们吧
underscore_d

原始发布者在系统类中使用new有点荒谬。(int *i; i = new int[9999];真的吗?int i[9999];要清晰得多。”)是的,虽然更清晰,但是扮演魔鬼的拥护者,类型不一定是一个不好的论点。对于9999个元素,我可以想象一个紧凑的嵌入式系统没有足够的堆栈来容纳9999个元素:9999x4字节为〜40 kB,x8〜80 kB。因此,假设这些系统使用替代内存来实现动态分配,则可能需要使用动态分配。不过,那只能证明动态分配是合理的,而不能new;一个vector是在这种情况下,真正的解决
underscore_d

同意@underscore_d-这不是一个很好的例子。我不会像那样将40,000或80,000字节添加到我的堆栈中。实际上,我可能会在堆上分配它们(std::make_unique<int[]>()当然有)。
einpoklum

3

两个原因:

  1. 在这种情况下是不必要的。您使代码不必要地变得更加复杂。
  2. 它在堆上分配空间,这意味着您以后必须记住delete它,否则将导致内存泄漏。

2

new是新的goto

回想一下为什么goto这么讨厌:尽管它是用于流控制的功能强大的低级工具,但是人们经常以不必要的复杂方式使用它,这使得代码难以遵循。此外,最有用和最容易阅读的模式是在结构化的编程语句(例如forwhile)中编码的;最终的结果是,在哪里找到goto合适的代码是非常罕见的,如果您很想编写代码,则goto可能做得不好(除非您真的知道自己在做什么)。

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

我甚至会认为new糟糕的goto,由于需要对newdelete报表。

就像goto,如果您认为自己需要使用new,那么您可能做错了事情-尤其是在类的实现之外这样做,而该类的实现的目的是封装您需要做的任何动态分配。


我要补充一点:“您基本上不需要它”。
einpoklum

1

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

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

但是,对于面向对象的代码,必须使用指针(这意味着new必须先创建指针)。为了简化资源管理的复杂性,我们提供了数十种工具来使它尽可能简单,例如智能指针。基于对象的范式或泛型范式采用值语义,并且需要更少或不需要new,就像其他地方的张贴者指出的那样。

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


4
这是一个糟糕的答案。For object oriented code, using a pointer [...] is a must废话。如果仅通过引用一个小的子集来贬低“ OO”,那么 多态性 - 也是废话:引用也可以。[pointer] means use new to create it beforehand尤其是废话:引用或指针可用于自动分配的对象,并且可以多态使用;看着我[typical OO code] use new a lot:也许在一本旧书中,但是谁在乎呢?任何依稀现代C ++避开new/原始指针尽可能- &是在没有办法这样做的任何较少OO
underscore_d

1

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

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

当然,这仅仅是规则的例外。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.