我在使用std::list时偶然发现堆栈溢出问题内存泄漏,其中一条评论说:
Stop using new so much. I can't see any reason you used new anywhere
you did. You can create objects by value in C++ and it's one of the
huge advantages to using the language. You do not have to allocate
everything on the heap. Stop thinking like a Java programmer.
我不太确定他说的是什么意思。为什么对象应该在C++中以尽可能频繁的方式创建,而它在内部又有什么区别呢?我误解了答案吗?
内存分配技术有两种:自动分配和动态分配。通常,每个都有一个对应的内存区域:堆栈和堆。好的。栈
堆栈总是按顺序分配内存。它可以这样做,因为它要求您以相反的顺序释放内存(先入后出:filo)。这是许多编程语言中局部变量的内存分配技术。它非常非常快,因为它需要最少的簿记,并且要分配的下一个地址是隐式的。好的。
在C++中,这称为自动存储,因为存储在范围结束时自动声明。一旦当前代码块(使用{}分隔)的执行完成,将自动收集该块中所有变量的内存。这也是调用析构函数清理资源的时刻。好的。堆
堆允许更灵活的内存分配模式。簿记更复杂,分配更慢。由于没有隐式释放点,您必须使用delete或delete[](c中的free手动释放内存。但是,缺少隐式发布点是堆灵活性的关键。好的。使用动态分配的原因
即使使用堆速度较慢并且可能导致内存泄漏或内存碎片,动态分配也有非常好的用例,因为它的限制较小。好的。
使用动态分配的两个主要原因:好的。
为什么不需要动态分配
在C++中有一个完整的构造,称为析构函数。此机制允许您通过将资源的生存期与变量的生存期对齐来管理资源。这种技术被称为RAII,是C++的识别点。它将资源"包装"成对象。std::string就是一个很好的例子。这个片段:好的。
1 2 3 4
| int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
} |
实际上分配的内存量是可变的。std::string对象使用堆分配内存,并在其析构函数中释放内存。在这种情况下,您不需要手动管理任何资源,仍然可以获得动态内存分配的好处。好的。
特别是,它意味着在这段代码中:好的。
1 2 3 4 5
| int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
} |
存在不需要的动态内存分配。程序需要更多的输入!!)并且会带来忘记释放内存的风险。这样做没有明显的好处。好的。为什么要尽可能频繁地使用自动存储
基本上,最后一段总结了这一点。尽可能频繁地使用自动存储使您的程序:好的。
- 更快的类型;
- 跑得更快;
- 不太容易发生内存/资源泄漏。
加分
在引用的问题中,还有其他问题。尤其是以下类别:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
} |
实际使用的风险要比以下风险大得多:好的。
1 2 3 4 5 6 7 8 9 10
| class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString ="foo_bar";
// note: there is a cleaner way to write this.
} |
原因是std::string正确地定义了一个复制构造函数。考虑以下程序:好的。
1 2 3 4 5
| int main ()
{
Line l1;
Line l2 = l1;
} |
使用原始版本,该程序可能会崩溃,因为它在同一字符串上使用delete两次。使用修改后的版本,每个Line实例将拥有自己的字符串实例,每个实例都有自己的内存,并在程序结束时释放这两个实例。好的。其他音符
由于上面的原因,RAII的广泛使用被认为是C++中的最佳实践。然而,还有一个额外的好处,并不是很明显。基本上,它比各部分的总和要好。整个机构组成。它有鳞片。好的。
如果使用Line类作为构建基块:好的。
1 2 3 4
| class Table
{
Line borders[4];
}; |
然后好的。
1 2 3 4
| int main ()
{
Table table;
} |
分配四个std::string实例,四个Line实例,一个Table实例,字符串的所有内容和所有内容自动释放。好的。好啊。
- 1 mentioning raii尽头,但应该有东西exceptions unwinding和堆栈。
- "东武:是啊,但这已经很长大后的冰,我想保持它为中心而出现的一个问题。"我会比IP写一篇博文和链接两个或什么它从这里。
- "与其他# 233;编辑:有趣的是:-)。"2 widely重新使用内存分配技术"……也许,在运行时,其他的briefly说全局/静态变量的存在为AA或更多或更好的。(3 widely过去式……?
- 你说这comprehensively,和更好的比可能已经"托尼,虽然是正确的。allocated变量的程序的开始是一个有趣的案例,三个不同的从它的栈或堆分配。
- "托尼,同样的东西五花八门:AA与东武,静态分配是一位显示了这个回复。我想让这个项目为中心,对出现的问题,提出"new煮上两个或两个new槽"。
- 会是一个伟大的补充说"下方为堆栈分配(至少直到C + + 1)-你需要两个或unnecessarily复制的东西,如果你不小心。例如一Monster吐出一Treasure的World当它死了。在其Die()方法adds《信息世界的宝藏。它必须使用在其他的两个world->Add(new Treasure(/*...*/))preserve the金银后它死了。这是shared_ptr替换(可能是矫枉过正,auto_ptr(普)为语义转移所有权的价值),通市(wasteful)和move+ unique_ptr(不widely implemented。)。
- "kizzx2:这问题已经addressed冰。如果allocated对象必须逃跑的一生,犹流动态分配的块,然后必须使用(即使它是间接的,如一份储存在一个向量)。
- 你所说的关于allocated堆栈的局部变量可能是一个小misleading。""栈"指的是调用堆栈,堆栈帧,商店。它的堆栈帧,这些服装是后进先出的仓库。"局部变量为一个特定的帧是allocated仿佛他们是一struct成员。
- someguy:德科工"的解释是不完美的。有自由的实施在其分配政策。然而,这两个变量是所需的initialized和破坏在一个后进先出的服装,所以《analogy团队。我认为它的工作complicating任何进一步的答案。
- 也许添加一个规则(注三Other notes之前的部分吗?我不知道如果这将是contaminating这个答案
- "哈#卡隆和233;你不能工作在你的第一个关键的动态分配,合理利用城市char array[variable];吗?我想这应该是第一个关键原因是,公司通常在堆栈大小的冰块。
- 有两个可能的另一个重要的原因是使用的堆内存的动态平衡如果size of存储器需要精确已知的冰。的对象可以是简单的,所以印刷纯粹的大尺寸的问题在创建它的堆栈。ideally一些代码分析工具,可以帮助城市预警当大对象就是placed onto堆栈存储器的程序,但也应该保持在这样的事件提醒。
因为这堆东西又快又简单
在C++中,只有一个指令来分配空间——在堆栈上——对于给定函数中的每个局部作用域对象,并且不可能泄漏任何内存。该注释打算(或者应该)说"使用堆栈而不是堆"。
- 除了它不wrote below the example。EEA的-你不能被分配到舱内舱on the栈;会有错误和堆栈不会帮助你。
- "分配空间只需要一条指令"——哦,胡说。当然,只需要一条指令就可以添加到堆栈指针中,但是如果这个类有任何有趣的内部结构,那么除了添加到堆栈指针之外,还会有更多的指令继续添加。同样有效的是,在Java中,没有分配空间的指令,因为编译器将在编译时管理引用。
- @查理是对的。自动变量速度快,万无一失更准确。
- @查理:这两种方法都需要设置类的内部结构。正在比较分配所需空间的情况。
- @奥利:考虑到这个简单的问题,我担心"自动变量"不会在操作人员的头脑中敲响任何警钟(或者那些稍后会偶然发现这个问题的人)。让我们说,栈被用作一种口语?
- @查理,请记住确切的原始问题。不知何故,操作人员似乎知道他对分配速度和建议的根本原因缺乏一个关键的洞察力。简单的问题。简单的回答。显然,我们可以更全面、更精确地涵盖C++(术语"自动变量")的术语,但这在某种程度上是不可能的,而且在某种程度上无助于此。
- 咳嗽int x; return &x;。
- @彼得:嘿,我知道有人会这么说。我们下节课讨论这个问题。:-)这是一个关于分配的问题。
- @DigitalRoss:我真的很惊讶以前没人这么做过:)
- 快是的。但肯定不是万无一失。没有什么是万无一失的。您可以得到stackoverflow:)
- 如果堆栈是万无一失的,那么Java将有堆栈分配!
- @Rxantos没有什么是万无一失的,因为无论你多么努力地去做万无一失的事情,他们总是会成为更好更好的,更好的,无论如何都会找到一种方法来炸毁它:p;)
- 偶然发现这篇文章,在这个背景下,int x; return &x;是怎么回事?为什么人们谈论这个?@彼得森
- @yuqli:在"栈是万无一失"的声明中插入一句话——从函数返回局部变量的地址是一个不太常见的错误(尽管它经常是伪装的,不像我发布的那样明显)。使用这样的指针将调用ub。
- @彼得森明白了。谢谢你的澄清。
这很复杂。
首先,C++不是垃圾回收。因此,对于每一个新的,都必须有相应的删除。如果您未能将此删除内容放入,则会出现内存泄漏。现在,对于这样一个简单的例子:
1 2 3
| std::string *someString = new std::string(...);
//Do stuff
delete someString; |
这很简单。但是如果"做事情"抛出异常会发生什么?糟糕:内存泄漏。如果"做东西"提早发布return,会发生什么?糟糕:内存泄漏。
这是最简单的情况。如果您碰巧将该字符串返回给某人,现在他们必须删除它。如果他们把它作为一个论点传递,接收它的人需要删除它吗?他们什么时候应该删除它?
或者,您可以这样做:
1 2
| std::string someString(...);
//Do stuff |
无delete。对象是在"堆栈"上创建的,一旦超出范围,它将被销毁。您甚至可以返回对象,从而将其内容转移到调用函数。可以将对象传递给函数(通常作为引用或常量引用:void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis))。诸如此类。
都没有new和delete。毫无疑问谁拥有内存或者谁负责删除内存。如果你这样做:
1 2 3
| std::string someString(...);
std::string otherString;
otherString = someString; |
据了解,otherString拥有someString的数据副本。它不是指针,而是一个独立的对象。它们可能有相同的内容,但您可以在不影响其他内容的情况下更改其中一个内容:
1 2
| someString +="More text.";
if(otherString == someString) { /*Will never get here */ } |
看到这个想法了吗?
- 在那张纸条上…如果一个对象是在main()中动态分配的,并且在程序运行期间存在,由于这种情况,在堆栈上不容易创建,并且指向它的指针被传递给任何需要访问它的函数,这会在程序崩溃时导致泄漏吗,或者它是安全的吗?我假设后者,因为操作系统释放程序的所有内存在逻辑上也应该释放它,但是我不想在涉及到new时假设任何东西。
- @Justintime您不必担心释放动态分配对象的内存,这些对象将在程序的生命周期中保留。当程序执行时,操作系统为它创建一个物理内存或虚拟内存的图集。虚拟内存空间中的每个地址都映射到物理内存的一个地址,当程序退出时,映射到其虚拟内存的所有内容都将被释放。所以,只要程序完全退出,就不需要担心分配的内存永远不会被删除。
由new创建的对象最终必须是deleted,以免泄漏。不会调用析构函数,不会释放内存,整个位。因为C++没有垃圾收集,这是个问题。
由值创建的对象(即堆栈上的对象)在超出范围时自动死亡。析构函数调用由编译器插入,函数返回时内存自动释放。
像auto_ptr和shared_ptr这样的智能指针解决了悬空的引用问题,但是它们需要编码规则,并且还有其他问题(可复制性、引用循环等)。
此外,在多线程情况下,new是线程之间的争用点;过度使用new可能会影响性能。堆栈对象的创建是通过定义线程本地创建的,因为每个线程都有自己的堆栈。
值对象的缺点是,一旦宿主函数返回,它们就会消失——您不能仅通过复制或返回值,将对这些对象的引用传递回调用方。
- + 1。"王newmust be created by =对象deleted漏以免他们还要糟。"匹配的delete[],new[]must be get by Undefined行为,如果你和你前deletenew[]和记忆和记忆delete[]newwarn about this(compilers甚少一些工具类方法可以给你当他们)。
- tonydelroy there are where the @情况编译器不能warn this。如果在函数返回的指针是在纽约,EN created if(单元)或[纽约]。
- C++不使用任何内存管理器。其他语言,如C.Y.,Java有垃圾收集器来处理内存。
- 使用操作系统例程分配内存和过多的新/删除可能会破坏可用内存。
- 对于任何应用程序,如果经常使用内存,建议在不需要时预先分配和释放内存。
- 不正确的内存管理可能导致内存泄漏,很难跟踪。因此,在函数范围内使用堆栈对象是一种行之有效的技术
- 使用堆栈对象的缺点是,它在返回、传递到函数等时创建多个对象副本。但是,智能编译器很清楚这些情况,并且已经对性能进行了很好的优化。
- 如果内存分配和释放在两个不同的地方,这真的很乏味。发布的责任始终是一个问题,我们主要依赖于一些常见的可访问指针、堆栈对象(尽可能多)和诸如auto-ptr(raii对象)之类的技术。
- 最好的是,您可以控制内存,最糟糕的是,如果我们对应用程序使用不正确的内存管理,您将无法控制内存。由于内存损坏而导致的崩溃是最糟糕和最难追踪的。
- 实际上,任何分配内存的语言都有一个内存管理器,包括C。大多数语言都非常简单,即int*x=malloc(4);int*y=malloc(4);…第一个调用将分配内存,也就是说请求操作系统分配内存(通常以块1K/4K的形式),这样第二个调用就不会实际分配内存,而是为您分配最后一个块。在我看来,垃圾收集器不是内存管理器,因为它只处理内存的自动释放。要称为内存管理器,它不仅要处理内存的释放,还要处理内存的分配。
- 局部变量使用堆栈,因此编译器不会发出对malloc()或其朋友的调用来分配所需的内存。但是,堆栈不能释放堆栈中的任何项,释放堆栈内存的唯一方法是从堆栈顶部展开。
我发现遗漏了尽可能少做新工作的几个重要原因:
运算符
new的执行时间不确定
调用new可能会或可能不会导致操作系统为您的进程分配一个新的物理页。如果您经常这样做,这可能会非常慢。或者它可能已经准备好了一个合适的内存位置,我们不知道。如果您的程序需要有一致和可预测的执行时间(如在实时系统或游戏/物理模拟中),那么您需要在时间关键的循环中避免使用new。
operator
new是一种隐式线程同步
是的,你听我说,你的操作系统需要确保你的页面表是一致的,因此调用new会导致你的线程获得一个隐式互斥锁。如果您总是从许多线程中调用new,那么您实际上是在序列化您的线程(我已经用32个CPU完成了这项工作,每个CPU访问new,以获得几百个字节,哎呀!这是一个皇家P.I.T.A.调试)
其他答案已经提到了其他问题,如速度慢、碎片化、容易出错等。
- Both can be avoided by using placement new/delete and allocating the memory before hand.或者你可以把你自己的记忆解放出来,而他们召唤的建造者/破坏者。This is the way std:vector usually works.
- @RXantos Please read op,this question is about avoiding unnecessary memory allocations.还有,这里没有满足感。
- @Emily This is what the op meant,I think:EDOCX1
- 使用Stack is not deterministic in execution time either.没有人叫你EDOCX1音标3或类似的东西。This is because the system might be running low on memory and the re're no ready physical memory pages available for the stack so the may need to swap or write some cakes(clear dirty memory)to disk before the execution can proceed.
- @Mikkorantalainen that's technically true but in a low memory situation all bets are off any way wrt performance a s you are pushing to disk so there is nothing you can do.It doesn't in any way involved the advice to avoid new calls when it is reasonable to do so.
前C ++ 17:因为即使用智能指针包装结果,它也容易发生细微的泄漏。
考虑一个"小心"的用户,他记得用智能指针包装对象:
1
| foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2())); |
此代码很危险,因为无法保证在T1或T2之前构造shared_ptr。因此,如果一个new T1()或new T2()中的一个在另一个成功之后失败,那么第一个对象将被泄漏,因为没有shared_ptr存在来破坏和释放它。
解决方法:使用make_shared。
C++ 17:
<罢工>这不再是一个问题:C++ 17对这些操作的顺序施加了约束,在这种情况下,确保每次调用EDCOX1〔7〕必须立即跟随相应的智能指针的构造,而在其间没有其他操作。这意味着,在调用第二个new()时,可以保证第一个对象已经被它的智能指针包装,从而在引发异常时防止任何泄漏。
由C++提供的新的评价顺序的更详细的解释是由巴里在另一个答案中提供的。
感谢@ Remy Lebeau指出这仍然是C++ 17下的一个问题(虽然不那么):EDCOX1×0的构造函数不能分配它的控制块并抛出,在这种情况下,传递给它的指针不会被删除。
解决方法:使用make_shared。
- 其他解决方案:永远不要动态地为每行分配多个对象。
- @锑:是的,当你已经分配了一个对象时,分配多个对象要比没有分配任何对象时更有吸引力。
- 我认为一个更好的答案是,如果调用了异常,而没有任何异常捕获到它,智能指针将泄漏。
- 即使在C++17之后的情况下,如果new成功,随后的shared_ptr构造失败,仍然可能发生泄漏。std::make_shared()也能解决这个问题。
- @Remylebau:很好,但我认为shared_ptr构造函数不能失败?
- @Mehrdd the EDOCX1 penographic 0 commercial constructor in question allocates memory for a control block that stores the shared point and deleter,so yes,it can theoretically throw a memory mistake.只有花花公子,移动,和合作建设者是非throwing。EDOCX1 original 1.Allocates the shared object inside the control block itself,so there is only 1 allocation instead of 2.
- @Remylebeau:OOOF,thank you for clarifying.I will update the answer.
在很大程度上,这是一个人把自己的弱点提升到一个普遍规律。使用new操作符创建对象本身没有任何错误。有一种观点认为,你必须用一些规则来实现这一点:如果你创建了一个对象,你需要确保它将被销毁。
最简单的方法是在自动存储中创建对象,因此C++知道当它超出范围时销毁它:
1 2 3 4 5 6
| {
File foo = File("foo.dat");
// do things
} |
现在,请注意,当您从端部支撑后的挡块上跌落时,foo超出范围。C++会自动调用它的DROR。与Java不同,您不需要等待GC找到它。
你写了吗?
1 2
| {
File * foo = new File("foo.dat"); |
你想把它和
或者更好的方法是,将您的File *分配为一个"智能指针"。如果你不小心,可能会导致泄漏。
答案本身就是错误的假设,如果你不使用EDCOX1,0,你就不会在堆上分配;事实上,C++中你不知道。至多,您知道一小部分内存(比如一个指针)肯定是在堆栈上分配的。但是,考虑文件的实现是否类似于
1 2 3 4 5
| class File {
private:
FileImpl * fd;
public:
File(String fn){ fd = new FileImpl(fn);} |
那么FileImpl仍将在堆栈上分配。
是的,你最好
在类中也是如此;没有它,即使在堆上根本没有明显分配内存,也会从堆中泄漏内存。
- You should take a look at the question the referenced队列中。当然有很多事情是错误的,要在队列。
- 有什么错与同意的new自己使用,但如果你看看原始的代码是在参考to the Comment is abused,new福利。is written the队列类是Java或C / #,where is used for every newpractically when things make变量,多更多。to be on the栈。
- 公平点。但通用的规则是normally avoid pitfalls enforced to Common。Whether this was an individuals weakness or not,is to权证管理复合存储足够的通用规则这样!):
- "安德烈,"卢克,一个新的misused that the事实证明一般不规则,你不应该使用新的。
- 查理:"You should say does not the Comment new永远不会使用。它说,如果你有一个选择自动配置和动态between the使用自动存储,存储。
- "现在你甚至更好的,分配的文件* as a"智能指针"。如果你是认真about that can"t en to泄密会更好。"值得一提的是智能分泄密之前,你implying as to the智能分是易泄密;-)。generally should each黑莓,任何内部分类generally handle that needs)在这样的方式是可靠方便的电话队列写的。"狡猾的"newdelete盎司/队列和encapsulated茶类,简单的和newtypically /无deletethe many callers队列中。
- 查理好@,but we need to justify不规则,说"不要使用情况newXYZ";我们need to justify规则,说"我在ABC new使用情况",因为newis using declaring比只是价值的努力。
- 查理:"there is wrong with new没有使用,但如果你使用delete做它,你错了!
- # Matthieu嘿,那是有趣的。告诉另一个。
- 查理:"让matthieu'固点-除非你真的在实施智能指针,然后使用一个好的选择automate is to to the memory分配我没有在你的deletelevel of队列。BTW idiom addresses)/ pimpl need beautifully无知,但熟悉的空气日期2010年1月17 std::stringwill be to a percentage of C++会借更多的programmers & & the immediacy在illustating强度问题。
- 托尼@我知道,如果你需要使用你的错误删除使用EN -除非它不是。
- 查理:确切的说因为是":-)"。嗯,我仍然会fileimpl allocated on the栈。"<!--你觉得"put to比堆栈。
- charliemartin:@"或甚至更好,你的File *as a分配的智能指针的……"他们工作在一些个案,但仍有subtle智能分时wrapping of the new输出问题。看到我的答案。
- 好的File foo("foo.dat");会更好
不应尽可能少地使用new()。应尽可能小心使用。它的使用频率应根据实用主义的需要而定。
在堆栈上分配对象,依赖于它们的隐式销毁,是一个简单的模型。如果一个对象所需的范围适合该模型,那么就不需要使用new(),使用相关的delete()并检查空指针。如果在堆栈上分配了大量短期对象,那么应该减少堆碎片问题。
但是,如果您的对象的寿命需要延长到当前范围之外,那么new()是正确的答案。只需确保您注意调用delete()的时间和方式以及空指针的可能性,使用已删除的对象以及使用指针时附带的所有其他gotcha。
- "如果对象的生存期需要扩展到当前范围之外,那么new()是正确的答案"…为什么不优先按值返回或接受由非constref或指针…定义的调用者范围的变量?
- @托尼:是的,是的!我很高兴听到有人鼓吹推荐信。它们是为了防止这个问题而创建的。
- "他们tonyd ...or组合:A市智能指针的返回值。这是呼叫方式和在许多用例(即在make_shared/_unique冰usable)被调用方不需要new或delete。这个答案的皇家点失误:(一)C + +提供该类的东西,移动和输出参数的语义,哪些行为或对象均是创造和延长寿命的城市allocated校正动态存储器成为unnecessary和无心。(b)在平衡的情况动态分配所需的冰,stdlib raii包装,提供用户relieve《丑女》内的细节。
使用new时,对象将分配给堆。它通常在您预期扩展时使用。当您声明一个对象时,例如,
它被放在堆栈上。
您将始终必须调用destroy来销毁您用new放置在堆中的对象。这会打开内存泄漏的可能性。放置在堆栈上的对象不容易发生内存泄漏!
- "[ 1 ] +堆当你提前展开generally used to a std::stringappending"样或std::map敏锐的洞察力,是的。我的反应是"but also初甚安object' decouple commonly to creating终身from the S S,但不同的扫帚"模式的校正值或值不接受呼叫方scoped -模式-或更好的参考指针constfor that is when"展开,除了有"involved,太。有一些其他的声音uses类工厂方法虽然…
避免过度使用堆的一个显著的原因是性能——具体涉及C++所使用的默认内存管理机制的性能。虽然在一般情况下分配速度很快,但是在不严格顺序的非均匀大小对象上进行大量的new和delete操作不仅会导致内存碎片化,而且会使分配算法复杂化,在某些情况下会完全破坏性能。
这就是创建内存池来解决的问题,允许减轻传统堆实现的固有缺点,同时允许您根据需要使用堆。
不过,最好还是完全避免这个问题。如果你能把它放在堆栈上,那么就这样做。
- 如果速度有问题,您总是可以分配相当大的内存,然后使用placement new/delete。
- 内存池是为了避免碎片化,加速释放(一个释放用于数千个对象),并使释放更安全。
我倾向于不同意使用新的"太多"的想法。尽管最初的海报使用了新的系统类有点可笑。(int *i; i = new int[9999];?真的?我认为这正是引起评论人愤怒的原因。
当您处理系统对象时,很少需要对完全相同的对象进行多个引用。只要价值是一样的,这就是一切。系统对象通常不会占用内存太多空间。(字符串中每个字符一个字节)。如果他们这样做了,库的设计应该考虑到内存管理(如果他们写得好的话)。在这些情况下(除了他代码中的一两条新闻之外),new实际上是毫无意义的,只会引入混乱和潜在的bug。
但是,当您使用自己的类/对象(例如原始海报的line类)时,您必须开始自己思考内存占用、数据持久性等问题。此时,允许对同一个值进行多个引用是非常宝贵的——它允许链表、字典和图表等结构,其中多个变量不仅需要具有相同的值,还需要引用内存中完全相同的对象。但是,行类没有这些要求。所以原来海报的代码实际上完全不需要new。
- 通常,当您不知道数组的大小时,使用new/delete。当然,std::vector会为您隐藏new/delete。您仍然使用它们,但是通过std::vector。所以现在,当您不知道数组的大小,并且出于某种原因希望避免std::vector的开销(虽然很小,但仍然存在)时,可以使用它。
- 你经常没有理由这样做!一小部分的质量保证是由熟练的编码人员在容器设计的细节。与此形成鲜明对比的是,令人沮丧的比例是,对于那些不知道stdlib存在的新手,或者在"编程"课程中积极地接受糟糕的作业,在那里导师要求他们毫无意义地重新发明轮子之前,他们甚至还没有学会轮子是什么以及为什么它起作用。通过促进更抽象的分配,C++可以将我们从C的"链表"无休止的"分段"中解救出来。
- "原始海报使用新的系统类有点可笑。(int *i; i = new int[9999];?真的?"是的,这是更清楚的,但要扮演魔鬼的拥护者,这种类型并不一定是一个坏论点。对于9999个元素,我可以想象一个紧凑的嵌入式系统没有足够的9999个元素的堆栈:9999x4字节是约40kb,x8~80kb。因此,这些系统可能需要使用动态分配,前提是它们使用备用内存来实现它。不过,这可能只能证明动态分配的合理性,而不是new;在这种情况下,vector才是真正的解决办法。
我想海报是说You do not have to allocate everything on theheap而不是stack。
基本上,对象是在堆栈上分配的(当然,如果对象大小允许的话),因为堆栈分配的成本很低,而不是基于堆的分配,它涉及分配器的相当多的工作,并且添加了冗长的内容,因为这样您就必须管理在堆上分配的数据。
原因有二:
在这种情况下是不必要的。你让你的代码变得不必要的复杂。
它在堆上分配空间,这意味着稍后必须记住delete,否则会导致内存泄漏。
核心原因是堆上的对象总是比简单的值更难使用和管理。编写易于阅读和维护的代码始终是任何严肃的程序员的首要任务。
另一个场景是我们正在使用的库提供了值语义并使动态分配变得不必要。Std::string就是一个很好的例子。
但是,对于面向对象的代码,必须使用指针(也就是说使用new预先创建它)。为了简化资源管理的复杂性,我们有许多工具可以使其尽可能简单,例如智能指针。基于对象的范式或通用范式假定值语义,并且需要较少或不需要new,正如其他地方的海报所述。
传统的设计模式,特别是在GOF书中提到的模式,使用了很多new,因为它们是典型的OO代码。
- 这是一个糟糕的答案。For object oriented code, using a pointer [...] is a must:胡说。如果你仅仅通过引用一个小的子集来贬低"oo",那么多态性——也是胡说八道:引用也起作用。[pointer] means use new to create it beforehand:特别是无意义的:引用或指针可以被用来自动分配对象和多态使用;观察我。以东十一〔八〕也许在旧书里,但谁在乎呢?任何模糊的现代C++在任何可能的地方避开EDCOX1的4个/原始指针——这样做绝不可能减少OO。
再指出上面所有的正确答案,这取决于你在做什么样的编程。例如,在Windows中开发内核时,堆栈受到严重限制,您可能无法像在用户模式中那样接受页面错误。
在这种环境中,新的或类C的API调用是首选的,甚至是必需的。
当然,这只是一个例外。
new是新的goto。
回想一下为什么goto被如此贬低:虽然它是一个强大的、低级的流控制工具,但人们经常以不必要的复杂方式使用它,这使得代码难以遵循。此外,最有用和最容易阅读的模式是用结构化的编程语句(如for或while编码的;最终的效果是,goto是合适的方式的代码非常罕见,如果你想写goto的话,你可能做得很糟糕(除非你真的知道你在做什么)。
new是类似的,它经常被用来使事情变得不必要的复杂和难以阅读,最有用的使用模式可以被编码到各种类中。此外,如果您需要使用任何还没有标准类的新使用模式,您可以编写自己的类来编码它们!
我甚至认为,由于需要将new和delete两种说法配对,new比goto更糟。
与goto一样,如果您认为需要使用new时,可能会做得很糟糕,尤其是在类的实现之外,这样做的目的是封装您需要执行的任何动态分配。
new在堆上分配对象。否则,对象将在堆栈上分配。看看这两者的区别。