我应该将所有对 new/delete 的调用集中到一个类吗?

Posted

技术标签:

【中文标题】我应该将所有对 new/delete 的调用集中到一个类吗?【英文标题】:Should I centralize all calls to new/delete to a single class? 【发布时间】:2011-09-18 00:16:07 【问题描述】:

目前,我的所有对象都管理自己的内存,通常在其构造函数中使用new 进行分配,并在其析构函数中使用delete。这目前有效,但我拥有的使用任意数量内存的类数量正在增长。 new 本质上是一个“请求”这一事实也让我感到困扰,因为这些对象中没有代码来处理被告知“不”的情况,如果我不需要,我不想依赖异常处理。

在性能方面是否有益于完全 将所有分配内存的调用屏蔽到一个处理 堆上的每个内存分配,可能分配大块 一次并使用placement new来处理参考?

在较小的类中使用内存分配是否是一个足够大的问题,甚至会为此烦恼?

我还能使用 STL 容器并强制它们使用我的堆吗 提供?

提前谢谢你!

【问题讨论】:

“这些对象中没有代码来处理被告知“不”,如果我不需要,我不想依赖异常处理”-你还想什么其他方式处理这个?您要么有支票,要么有例外。内存不是无限的,所以没有其他选择。 @merlyn - 如果我将它集中到一个类中,检查将根据请求在内存管理类中进行。然后我只需要在那里放置所有需要的代码。例如,我可以提示用户释放内存,或者在程序尝试再次分配内存时暂停程序。或者只是在那个时候终止。 您仍然拥有所有这些异常处理选项,并且您可以将代码放在它所属的位置 - 了解系统中正在发生的事情的更高级别的代码(因此可以进行释放然后重试操作,或向用户显示消息)。当然你可以争辩说你的分配器应该有一个“检查是否有足够空间”的方法,所以你可以在抛出异常之前做到这一点...... “例如,我可以提示用户释放内存,或者在程序尝试再次分配内存时停止程序。或者直接终止”——这就是std::set_new_handler 的用途。另请注意,在内存不足的情况下很难向用户显示消息,您必须确保提前分配了所有必要的资源。 @Steve - 我不知道它存在。那是...正是我一直在寻找的,谢谢! (我想为了显示一条消息,在一开始就保留内存以供关键使用会有所帮助) 【参考方案1】:

我还能使用 STL 容器并强制它们使用我提供的堆吗?

STL 容器接受自定义分配器:

http://en.wikipedia.org/wiki/Allocator_(C%2B%2B)#Custom_allocators

这是一个带有示例链接的线程:

Compelling examples of custom C++ allocators?

在性能方面是否有益...?

您只能通过编写应用程序、提出一组可重现的测试场景以及在分析器中运行代码来找出答案。如果您发现内存分配占运行时间的很大一部分,那么您可能会从更好的分配策略中受益。

如果您可以将程序分解为功能级别,并且可以针对每种情况提出现实场景,那么您不必让整个程序都工作来执行此操作。但是请记住,花在优化上的时间是可以花在测试或编码新功能上的时间:) 只做必要的事情......

在较小的类中使用内存分配是一个足够大的问题,甚至会为此烦恼吗?

根据您的程序,您对大分配故障的敏感程度,您在循环中分配的频率等,这是可能的。简介:)

在开发应用时,您仍然可以对分配敏感 - 尽可能创建自动存储(堆栈本地)变量,并仅在必须时动态分配。

【讨论】:

明白!我的分析器仅将其视为某些物理内容的接触对生成中的一个小问题。不过此时,我只想将我的错误检查代码放在某个地方,所以我不必将样板代码放在任何地方。性能提升只是我很好奇的事情 @Clairvoire:您还可以考虑将错误检查代码放在工厂方法/类中。【参考方案2】:

我不确定我是否理解您的问题。也就是说,在 C++03 中将 STL 容器与您的自定义堆一起使用将具有挑战性,因为分配器被认为是无状态的。另外,你为什么不想依赖异常处理呢?你知道有一个 no_throw 版本的 new 吗?

编辑: new 的无抛出版本调用如下:new (std::nothrow) Type[size];。如果分配失败,它会返回一个空指针(0)而不是抛出std::bad_alloc

【讨论】:

我不知道 :) 你如何调用new 的 no_throw 版本?您可以将此添加到您的答案中吗? 我知道,但是使用 no_throw 意味着我必须检查 NULL。本身不是问题,但是该级别的类通常无法从被拒绝的内存中恢复。至于异常处理,只是偏好而已。【参考方案3】:

我还能使用 STL 容器并强制它们使用我提供的堆吗?

是的,查找 STL allocators。

完全屏蔽所有分配内存的调用对处理堆上每个内存分配的单个类(可能一次分配大块并使用placement new处理引用)在性能方面有益吗?

这基本上就是mallocnew 的良好实现。我不建议您自己做,除非情况非常对性能非常关键并且其他所有内容都已优化。为什么?因为经过深思熟虑的内存管理即使没有错误也很难,更不用说优化工作了。

在较小的类中使用内存分配是一个足够大的问题,甚至会为此烦恼吗?

这取决于您是否正在为具有 16k 内存的咖啡机或游戏设备进行编程,但在普通台式电脑或笔记本电脑上可能不会。另请记住,堆栈非常快(分配和访问),而堆在分配和使用方面稍差(不太确定 TBH),因此在日常情况下您希望更喜欢堆栈。

【讨论】:

...或者它是瓶颈,所以不要先优化其他东西 @Merlyn Morgan-Graham:是的 :) +1;至于目标系统的东西(咖啡机),主机视频游戏系统也受益于谨慎的分配策略,因为开发人员倾向于将每个资源预算榨干,而不是“我们会做我们想做的事情并告诉用户升级” 【参考方案4】:

你说你在你的构造函数中调用new...真的有必要吗?我的意思是,而不是这个......

class A
    std::vector<int>* v;
    A( int vectorSize )
       v = new std::vector<int>( vectorSize, 0 );
    
    ~A()
       delete v;
    
;

...这样做总是比较可取的:

class A
    std::vector<int> v;
    A( int vectorSize ):
       v( vectorSize, 0 )
    
;

这样可以避免使用堆。只有在别无选择时才应该使用堆。

除此之外,就像其他人所说的那样,编写自定义分配器是一项非常复杂的任务,只能在性能关键的场景中完成

【讨论】:

std::vector&lt;int&gt; v; 在堆栈上分配容器本身,但我一直认为容器使用的实际内存是在堆上创建的。 啊,现在我明白你的意思了。由于vectors 需要动态增长,他们别无选择,只能使用堆。虽然如果他们在堆栈上分配足够的初始空间,他们可能会避免它,并且只有在超过该限制时才请求堆空间。无论如何,这是一个非常特定于实现的细节。 Visual Studio 的做法可能与 gcc 不同。 +1;赞成,因为这是一个很好的建议,即使仔细检查不适用。【参考方案5】:

我以前做过,这是我的经验:

    它很快就会变得非常混乱。特别是因为您现在将内存分配掌握在自己手中,并且您必须处理诸如碎片和重入之类的事情。 但是,由于能够绕过操作系统开销,我发现性能提升超过 20%

对于您的最后一个问题,我认为有一种方法可以让他们使用自定义分配器,但我以前从未这样做过。我的大部分编码都是用 C 语言完成的。

编辑:

根据您的评论,这里有一个更新。您实际上不必处理构建分配器。您可能只需将所有内存分配都指向您的自定义类即可。然后,您的自定义类将调用 malloc()new 并捕获返回的任何 NULL 或异常。

(虽然将每个new 替换为您自己的malloc() 需要一些工作。)

【讨论】:

以上是关于我应该将所有对 new/delete 的调用集中到一个类吗?的主要内容,如果未能解决你的问题,请参考以下文章

创建类时我应该在哪里放置new / delete

MinGW 中的全局重载运算符 new/delete

new/malloc

C++面试题

新/删除不匹配

是否可以将多个数据库中的多个 django 查询集合并到一个查询集中?