18

我只是在回答一个关于缺少与新位置相对应的位置删除的问题。原因似乎operator delete是根据对象的动态类型(对应于用于 find 的类型operator new)调用方式。

安置new对我很有用。当涉及到自定义分配时,可能有一个可重用的类,其中不同的实例管理不同的池。单例是一种反模式等等。

我可以理解在new thing;不跟踪分配器的情况下进行工作的便利性,但是为类型层次结构的不同分支做事似乎相当复杂。

是否存在派生类使用与其基类不同的分配器并依赖虚拟析构函数来查找正确成员的真实场景operator delete

以免这是主观的,我会接受最合理的答案。让我们不要对代码气味或“最好”的做事方式争论不休。

4

4 回答 4

8

我过去真的用过这个!它对于非统一的内存架构很有用——没有操作系统的内存区域非常小、非常快的平台等。

具体来说,设想一个带有少量 TCM(紧密耦合存储器;本质上是嵌入在 SoC 上的 SRAM,例如具有 1 个周期的访问时间)的 ARM 芯片。

然后,我们在产品开发的最后阶段使用分析器结果——就在发货之前(例如,想象这是一个流行的手持游戏系统的卡带)——以确定某些类别将受益于这种更快的 SRAM。

一个利用这个 TCM 来处理派生类的简单成员operator new现在可能是有意义的:我们不能使用这个 SRAM 来拥有整个类层次结构,但是对于某些低实例化计数但大量使用的派生类,它变成一个简单有效的优化。在某些情况下,通过以这种方式重定向某些分配,我们已经恢复了 2%-10% 或更多的帧时间。

于 2013-05-04T18:15:04.270 回答
5

我实际上从未在派生类中使用过 new/delete 重载,或者我曾经想过它,但这个问题很有趣,我决定进行一些研究并试一试。我找到了几个合适的参考:

ARM http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka14267.html

这个参考实际上有一个派生类重载其基类重载新的示例代码。

范德比尔特大学 http://www.vuse.vanderbilt.edu/~adamsja/Courses/CS251/Projects/4/memPool.pdf

该参考资料没有明确提供有关在派生类中重载 new 的任何材料,但是,它提到了一些有趣的原因,即为什么要重载 new。这些原因包括:

  • 补偿默认分配器中的次优对齐
  • 将相关对象聚集在一起
  • 获得非常规行为,例如用零覆盖内存以增加应用程序数据的安全性

现在基于这两个参考,我认为在派生类中重载 new/delete 可能有几个原因。我的理由与我在 VU 演示文稿中列出的理由基本一致,但基于 ARM 参考似乎也相关,这对我来说是嵌入式或专用场景。

  • 嵌入式系统在硬件级别通常是非常定制的,但是在软件级别的标准化意义上是常见的。我可以看到一种情况,根据配置设置/硬件配置,在运行时选择了某种类型的对象(派生类),因为它需要以特定方式或特定位置分配内存。
  • 聚类似乎是合理的,因为我可以设想这样一种场景,即某些对象在高级“工作流程”中被同等对待,但在处理时,这些对象的不同类型的处理方式却大不相同。对于处理密集型任务(即搜索、排序、优先排序等),将特定类型的对象定位在彼此附近可能是有益的。
  • 最后一点很有趣,因为我可以看到分配取决于所存储信息的安全分类的某些情况。

同样,我还没有找到原因或实际实施过您提到的某些事情,但这些是我经过一番调查后的想法。

于 2013-05-02T21:31:57.823 回答
1

是否存在派生类使用与其基类不同的分配器并依赖虚拟析构函数来查找正确成员的真实场景operator delete

我不确定你会认为现实世界的场景是什么,但我想到的一个明显的例子是继承层次结构,它植根于抽象基类,派生类有很大不同(大小),其中很多是定期创建和销毁,而这些分配/解除分配需要速度。

您可能需要为这些派生类自定义分配器,因为用于特定大小的内存 blob 的分配器可能非常快,并且您可能希望为每个派生类使用不同的分配器,因为它们的大小非常不同。

不过,我不能给你一个具体的例子,因为多年来我发现避免分配/解除分配比加速它的回报要好得多,所以在近 20 年里,我很少重载特定于类的new/ delete。(当然,当我们谈论复杂的优化时,通常会出现“游戏”,所以我们可以想象一个需要创建和摧毁大量不同实体的游戏。)

于 2013-05-02T18:07:23.697 回答
0

成员operator new/delete可能有助于从基类中删除不需要的自定义分配,例如,如果基类不打算扩展。但是基础要么需要一个虚拟析构函数,要么基础对象永远不能是delete.

实际上,operator delete需要独立于重载的虚拟析构函数调度。该功能的存在可能是为了支持多重继承,而动态查找正确的成员operator delete只是一个副作用。

规则很简单,最派生对象的虚拟析构函数调用operator delete. 主要原因是传递正确的最衍生指针和大小。上下文还使程序能够动态地找到正确的释放函数只是一个令人愉快的副作用。

因此,动态调度可能有也可能没有有用的应用程序delete,但绝对没有专门的机制让它工作。

简单演示

struct pad {
    int x;

    virtual ~pad() {}
};

struct b {
    int x;
};

struct vb {
    int x;

    virtual ~vb() {}
};

struct d : pad, b, vb {};

void operator delete( void *p ) {
    std::cout << "free " << p << '\n';
}

int main() {
    std::cout << "With virtual destructor:\n";
    d *p = new d;
    std::cout << "allocate " << p << ", delete " << static_cast< vb * >( p ) << '\n';
    delete static_cast< vb * >( p );

    std::cout << "With plain destructor:\n";
    p = new d;
    std::cout << "allocate " << p << ", delete " << static_cast< b * >( p ) << '\n';
    delete static_cast< b * >( p );
}
于 2013-05-03T02:26:17.193 回答