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

江枫思渺然 提交于 2020-01-06 17:03:26

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

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

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

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


#1楼

C ++ 17之前的版本:

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

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

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

该代码很危险,因为不能保证 T1T2 之前构造了shared_ptr 。 因此,如果new T1()new T2()的一个在另一个成功之后失败,则第一个对象将被泄漏,因为不存在用于破坏和重新分配它的shared_ptr

解决方案:使用make_shared

后C ++ 17:

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

\n

Barry 在另一个答案中提供了对C ++ 17引入的新评估顺序的更详细说明。

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

解决方案:使用make_shared


#2楼

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

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

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

运算符new是一个隐式线程同步

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

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


#3楼

new是新的goto

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

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

我什至认为new是比goto 更糟糕的 ,因为需要将newdelete语句配对。

goto一样,如果您曾经认为需要使用new ,则可能做得不好-尤其是在类的实现之外,这样做的目的是封装所有您需要执行的动态分配。


#4楼

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

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

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


#5楼

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

Class var;

它放在堆栈上。

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!