派生类中的成员new / delete重载是否有用?

时间:2013-01-02 10:15:04

标签: c++ inheritance operator-overloading new-operator

我刚刚回答了a question关于缺少与展示位置相对应的展示位置删除的问题。原因似乎是根据对象的动态类型调用operator delete的方式(对应于用于查找operator new的类型)。

展示位置new对我有用。在自定义分配方面,可能存在一个可重用的类,其中不同的实例管理不同的池。单身人士是一种反模式,所有这一切。

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

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

为了避免这种主观,我会接受最合理的答案。让我们不要对代码气味或“最佳”做事方式嗤之以鼻。

4 个答案:

答案 0 :(得分:8)

我过去实际使用过这个!它对非均匀内存架构很有用 - 平台具有非常小的非常快的内存区域而没有操作系统等。

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

然后我们在产品开发的最后阶段使用分析器结果 - 就在发货之前(例如,想象一下这是一个流行的手持游戏系统的盒式磁带) - 确定某些类可以从这个更快的SRAM中受益

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

答案 1 :(得分:5)

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

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

这个引用实际上有一个派生类的示例代码,它重载了它的基类重载new。

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

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

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

现在基于这两个引用,我已经确定在派生类中重载new / delete可能有一些原因。我的理由基本上与我在VU演示文稿中列出的原因一致,但基于ARM参考暗示我的嵌入式或专用场景也似乎相关。

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

同样,我还没有找到理由或实际执行过你提到过的事情,但这些是我的想法,只需要一点调查。

答案 2 :(得分:1)

  

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

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

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

但是,我不能给你一个具体的例子,因为多年来我发现避免分配/解除分配的方式比加速它更好,所以近二十年我很少重载特定于类的new / delete。 (当然,正如我们谈论复杂的优化时一样,“游戏”出现了,所以我们可以想象一个游戏需要创建并摧毁大量非常不同的实体。)

答案 3 :(得分:0)

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

实际上,operator delete的虚拟析构函数调度与重载无关。可能存在支持多重继承的功能,动态查找正确的成员operator delete只是副作用。

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

因此,动态delete调度可能有也可能没有用,但绝对没有专门的机制来使其发挥作用。

Simple demo

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 );
}