我偶然发现了堆栈溢出问题,使用std :: list 时出现std :: string内存泄漏,其中一条评论说:


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


我不确定他的意思。


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

/>

#1 楼

有两种广泛使用的内存分配技术:自动分配和动态分配。通常,每个都有一个对应的内存区域:堆栈和堆。
Stack
堆栈始终以顺序方式分配内存。这样做是因为它要求您以相反的顺序释放内存(先进先出: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实例将拥有自己的字符串实例,每个实例都具有自己的内存,并且都将在程序结尾处释放。
其他说明
广泛使用RAII被认为是最好的方法由于上述所有原因,请在C ++中进行练习。但是,还有其他好处尚不明显。基本上,它比各部分的总和要好。整个机制组成。
如果将Line类用作构建块:
 class Table
 {
      Line borders[4];
 };

然后
 int main ()
 {
     Table table;
 }
std::string实例,四个Line实例,一个Table实例和一个所有字符串的内容以及所有内容都会自动释放。

评论


在末尾提到RAII时为+1,但是应该有一些关于异常和堆栈展开的内容。

–东武
2011年6月28日在18:36

@Tobu:是的,但是这篇文章已经很长了,我想让它重点放在OP的问题上。我最终将写一篇博客文章或其他内容,然后从此处链接到它。

–AndréCaron
2011年6月28日19:18

提到堆栈分配的缺点(至少要到C ++ 1x为止)将是一个很好的补充-如果您不小心,则经常需要不必要地复制内容。例如怪物死后向世界吐出宝藏。在其Die()方法中,它向世界添加了宝藏。它必须在死后使用world-> Add(new Treasure(/*...*/))来保存该宝藏。替代方案是shared_ptr(可能会过大),auto_ptr(所有权转移的语义较差),按值传递(浪费)和move + unique_ptr(尚未广泛实施)。

– kizzx2
2011年6月30日12:09

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

–someguy
11年8月15日在19:17

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

–AndréCaron
2011年8月15日在20:09

#2 楼

由于堆栈速度更快且防泄漏

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

评论


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

–查理·马丁(Charlie Martin)
11年6月28日在0:33

@查理是正确的。自动变量是快速的,万无一失将更加准确。

–奥利弗·查尔斯沃思(Oliver Charlesworth)
2011-6-28 at 0:35

@Charlie:两种方法都需要设置类的内部。在分配所需空间方面进行了比较。

–奥利弗·查尔斯沃思(Oliver Charlesworth)
11年6月28日在0:36

咳嗽int x;返回&x;

–peterchen
2011年6月29日13:29

快是的。但是肯定不是万无一失的。没有什么是万无一失的。你可以得到一个StackOverflow :)

–rxantos
15年2月12日在6:25

#3 楼

原因很复杂。

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

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


这很简单。但是,如果“东西”抛出异常怎么办?糟糕:内存泄漏。如果“执行任务”提前发出return,会发生什么情况?糟糕:内存泄漏。

这是最简单的情况。如果您碰巧将该字符串返回给某人,则现在他们必须删除它。并且,如果他们将其作为参数传递,那么接收它的人是否需要删除它?他们什么时候应该删除它?或者,您可以执行以下操作:

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


没有delete。对象是在“堆栈”上创建的,一旦超出范围,它将被销毁。您甚至可以返回对象,从而将其内容传输到调用函数。您可以将对象传递给函数(通常作为引用或const引用: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()中动态分配了一个对象,则该对象在程序的整个过程中都存在,由于这种情况,不能轻易在堆栈上创建该对象,并且将其指针传递给需要访问它,这是否可能在程序崩溃的情况下导致泄漏,还是安全的?我会假设是后者,因为OS分配所有程序的内存也应该在逻辑上对其进行分配,但是当涉及到新的东西时,我不想承担任何责任。

–贾斯汀时间-恢复莫妮卡
16-2-4在19:19



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

–艾曼·埃里亚尼(Aiman Al-Eryani)
16-2-8在15:44



#4 楼

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

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

unique_ptrshared_ptr之类的智能指针解决了悬而未决的引用问题,但它们需要编码规则并且还存在其他潜在问题(可复制性,引用循环等)。

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

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

评论


+1。关于“由new创建的对象必须最终删除,以免它们泄漏。” -更糟糕的是,new []必须与delete []匹配,并且如果删除new []-ed内存或delete [] new-ed内存,则会得到未定义的行为-很少有编译器对此发出警告(某些工具,例如Cppcheck会当他们可以的时候)。

–托尼·德罗伊(Tony Delroy)
2011年6月28日在1:21

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

–fbafelipe
2012年6月27日在1:30

#5 楼


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


评论


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

–拉利
15年6月23日在22:45

局部变量使用堆栈,因此编译器不会发出对malloc()或其朋友的调用来分配所需的内存。但是,堆栈不能释放堆栈中的任何项目,释放堆栈内存的唯一方法是从堆栈顶部展开。

– Mikko Rantalainen
18-10-12在7:21

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

– einpoklum
19年7月28日在20:40

#6 楼

我发现错过了执行尽可能少的新操作的几个重要原因:

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

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

运算符new是隐式线程同步

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

其他答案已经提到了诸如慢速,碎片化,容易出错等之类的其他问题。

评论


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

–rxantos
15年2月12日在6:40

@rxantos请阅读OP,该问题与避免不必要的内存分配有关。另外,没有放置删除。

–艾米莉·L。
2015年2月12日在16:54

@Emily,这就是OP的含义,我认为:void * someAddress = ...;删除(T *)someAddress

–xryl669
18年1月16日在18:44

使用堆栈在执行时间上也不是确定的。除非您已调用mlock()或类似名称。这是因为系统可能内存不足,并且堆栈没有可用的物理内存页面,因此OS可能需要先交换或向磁盘写入一些缓存(清除脏内存),然后才能继续执行。

– Mikko Rantalainen
18-10-12在7:23

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

–艾米莉·L。
18-10-13在11:45

#7 楼

C ++ 17之前的版本:
因为即使将结果包装在智能指针中,它也容易发生细微的泄漏。请考虑一个“谨慎”的用户,他记得将对象包装在智能指针中:
foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));

此代码很危险,因为不能保证shared_ptr的构造要早于T1T2。因此,如果new T1()new T2()中的一个在另一个成功之后失败,则第一个对象将被泄漏,因为不存在shared_ptr可以销毁和取消分配它。
解决方案:使用make_shared
Post-C ++ 17 :
这不再是问题:C ++ 17对这些操作的顺序施加了约束,在这种情况下,确保对new()的每次调用都必须紧随其后构造相应的智能指针,而不会之间的其他操作。这意味着,在调用第二个new()时,可以确保第一个对象已经包装在其智能指针中,从而防止在引发异常的情况下发生任何泄漏。
对此的详细说明Barry在另一个答案中提供了C ++ 17引入的新评估顺序。
@Remy Lebeau指出在C ++ 17下这仍然是一个问题(尽管不是这样):shared_ptr构造函数可以无法分配其控制块并抛出,在这种情况下,传递给它的指针不会被删除。
解决方案:使用make_shared

评论


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

–锑
13年8月16日在4:30

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

–user541686
13年8月16日在4:44

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

–娜塔莉·亚当斯(Natalie Adams)
2013年9月16日下午4:34

即使在C ++ 17后的情况下,如果新方法成功执行,随后的shared_ptr构造也会失败,仍然可能发生泄漏。 std :: make_shared()也可以解决这个问题

–雷米·勒博(Remy Lebeau)
19 Mar 15 '19在0:47

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

–雷米·勒博(Remy Lebeau)
19 Mar 15 '19 2:13



#8 楼

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

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

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

评论


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

–托尼·德罗伊(Tony Delroy)
2011年6月28日在1:32

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

–内森·奥斯曼(Nathan Osman)
11年6月28日在23:48

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

– underscore_d
16年7月30日在13:11



#9 楼

在很大程度上,这是将自己的弱点提升为一般规则的人。使用new运算符创建对象本身没有任何错误。有一种说法是必须遵循一定的纪律:如果创建对象,则需要确保将其销毁。

最简单的方法是创建该对象处于自动存储状态,因此C ++知道在超出范围时会销毁它:

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

    // do things

 }


现在,请注意,当您在结束后从那个块上掉下来时,大括号,foo超出范围。 C ++将自动为您调用其dtor。与Java不同,您无需等待GC找到它。

您是否已编写

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


您希望将其与

     delete foo;
  }


甚至更好,将您的File *分配为“智能指针”。

答案本身就做出了一个错误的假设,即如果您不使用new,则不会在堆上进行分配;如果不使用FileImpl,则不会在堆上进行分配。实际上,在C ++中您并不知道。最多,您知道肯定会在堆栈上分配一小段内存,例如一个指针。但是,考虑一下File的实现是否类似于

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


,那么q4312079q仍将分配在堆栈上。

是的,您最好确保在课堂上也有

     ~File(){ delete fd ; }


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

评论


您应该查看所引用问题中的代码。该代码中肯定有很多错误。

–AndréCaron
2011-6-28在0:13



我同意使用new本身没有任何问题,但是如果您查看注释所引用的原始代码,就会滥用new。代码就像Java或C#一样编写,当事情在堆栈中变得更有意义时,实际上将new用于每个变量。

– luke
11年6月28日在0:15

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

–Robben_Ford_Fan_boy
11年6月28日在0:17

@Charlie:评论并不表示您永远不应该使用new。它表示,如果您可以在动态分配和自动存储之间进行选择,请使用自动存储。

–AndréCaron
2011年6月28日在0:54

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

– Matthieu M.
2011年6月28日下午6:51

#10 楼

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

Class var;


之类的对象时,它将被放置在堆栈上。您将其与新堆放在一起。这为内存泄漏打开了可能。放置在堆栈上的对象不容易发生内存泄漏!

评论


+1“ [heap]通常在您预期扩展时使用”-就像附加到std :: string或std :: map一样,是的,敏锐的洞察力。我最初的反应是“但也很普遍地将对象的生命周期与创建代码的作用域脱钩”,但实际上,通过值返回或通过非const引用或指针接受调用方作用域的值会更好,除非存在“扩展”也参与其中。但是还有其他声音用途,例如工厂方法。

–托尼·德罗伊(Tony Delroy)
2011年6月28日在1:28



#11 楼

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

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

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

评论


您始终可以分配合理数量的内存,然后在速度有问题时使用new / delete放置。

–rxantos
2015年2月12日下午6:33

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

–乐天
15年11月11日在21:20

#12 楼

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

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

#13 楼

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

当您使用系统对象时,很少需要一个以上的对象引用完全相同的对象。只要值相同,就很重要。系统对象通常不会占用太多内存。 (每个字符一个字节,一个字符串)。如果这样做的话,这些库的设计应考虑到该内存管理(如果编写得当)。在这些情况下(除了代码中的一两个新闻),new实际上是没有意义的,只会引起混乱和潜在的bug。

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

评论


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

–rxantos
2015年2月12日下午6:31

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

– underscore_d
16年7月30日在13:17



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

– underscore_d
18-10-27在18:20



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

– einpoklum
19年7月28日在20:46

#14 楼

有两个原因:


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


#15 楼

new是新的goto

回想一下为什么goto如此被人vil病:虽然它是功能强大的低级流量控制工具,但人们经常以不必要的复杂方式使用它,从而使代码难以理解。此外,最有用和最容易读取的模式是在结构化的编程语句(例如forwhile)中编码的;最终的结果是,使用goto是适当方法的代码很少见,如果您很想编写goto,则可能做得不好(除非您真的知道自己在做什么)。

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

我什至认为newgoto更糟,由于需要配对newdelete语句。

goto一样,如果您曾经认为需要使用new,则可能做得不好-尤其是如果您在执行以下操作之外一个类,其目的是封装您需要做的任何动态分配。

评论


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

– einpoklum
19年7月28日在20:47

也许可以举一个可以代替new使用的构造示例。

– gmatht
6月11日15:41

#16 楼

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

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

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

评论


这是一个糟糕的答案。对于面向对象的代码,必须使用指针[...]。如果仅通过引用一个小的子集来贬低“ OO”,那么多态性-也是废话:引用也可以。 [pointer]意味着可以使用new预先创建它:尤其是废话:可以将引用或指针用于自动分配的对象,并且可以多态使用;看着我。 [典型的OO代码]使用了很多新内容:也许在一些旧书中,但是谁在乎呢?任何模糊的现代C ++都尽可能避开new / raw指针-这样做绝对不会减少OO

– underscore_d
16 Jul 30 '13:36



#17 楼

还有一个以上所有正确答案的要点,这取决于您正在执行哪种编程。例如,在Windows中开发内核->堆栈受到严格限制,您可能无法像在用户模式下那样发生页面错误。在这种环境下,首选新的或类似C的API调用甚至是必需的。

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

#18 楼

new在堆上分配对象。否则,将在堆栈上分配对象。查找两者之间的区别。