【问题标题】:c++ delete should do nothingc++ delete 应该什么都不做
【发布时间】:2014-07-07 19:31:56
【问题描述】:

问题:

有没有办法让 delete 表现得像一个假人,即在调用时什么也不做?

为什么我需要答案:

我正在使用自定义内存池,当调用静态方法Pool::freeAllObjects() 时,它会从内存中释放所有对象。

所有类都有一个重载的运算符 new,如下所示:

void * operator new (size_t size)
{
void * obj = Pool::memory()->allocate(size);
Pool::memory()->save_destructor((ClassName*)obj);
return obj;
}

Pool::memory()->save_destructor() 只是保存了一个指向运行泛型析构函数的函数的指针。

如果没有人对使用池创建的对象调用delete,那么一切都会正常运行,但是在我们想要使用它的代码中,有许多对象实例都调用了delete,因此为了向后兼容我试图像这样制作一个重载的delete

void operator delete(void*) {/*do nothing*/}

在具有重载new 的类中使用Pool::memory() ,但看起来这并没有解决问题。我使用了一个简单的 std::cout 来查看调用的 cons/destructor 和一个重载的删除,例如:

void operator delete(void*) {std::cout << "deleting ClassName" << std::endl;}

所以在这样的代码中:

ClassName * instance = new ClassName();
instance->runMethod();
delete instance; //this is legacy code calling delete before the pool was developed
/*
 other code goes here
*/
Pool::freeAllObjects(); // oops instance was already deleted

我得到以下输出:

constructing ClassName

destructing ClassName

deleting ClassName

destructing ClassName

编辑: 抱歉,我没有提到要求是不使用智能指针,并且也不允许将 operator delete 设为私有(顺便说一句,我认为这是最好的选择)。

【问题讨论】:

  • 你不能阻止析构函数在使用 delete 时被调用。您可以修改operator delete,但删除是一个两步过程:调用对象的析构函数然后调用operator delete。您可以将 unique_ptr/shared_ptr 与自定义删除器一起使用,该删除器实际上并不调用 delete,并且永远不会处理原始指针或显式调用 delete。

标签: c++ memory-management operator-overloading


【解决方案1】:

你不能阻止delete调用析构函数;那是 它能做什么。但既然你正在赶上operator delete 稍后调用的函数,您可以使用某种隐藏的 旗帜。类似的东西:

union MemoryHeader
{
    bool hasBeenDeleted;
    double forAlignment;
};

void* operator new ( size_t size )
{
    MemoryHeader* hdr = static_cast<MemoryHeader*>(
                Pool::memory()->allocate( size + sizeof( MemoryHeader ) ) );
    hdr->hasBeenDeleted = false;
    void* obj = hdr + 1;
    Pool::memory()->save_destructor( (ClassName*)hdr );
    return obj;
}

void operator delete( void* obj )
{
    MemoryHeader* hdr = static_cast<MemoryHeader*>( obj ) - 1;
    hdr->hasBeenDeleted = true;
}

然后,当您运行删除程序时,您可以检查标志。

在您的情况下,或者甚至更好;在你的operator delete 函数,只需取消注册对象的析构函数:添加 一个 clear_destructor 函数到 Pool::memory() 返回的内容, 并调用它。

【讨论】:

  • 你为什么要在void* obj = hdr + 1; 上向 hdr 添加 +1,然后在删除时减去它,为什么不只是 void* obj = hdr; 呢?
  • 考虑 MemoryHeader 使用的空间。
  • @jhoffman0x 为什么要添加 +1 而不是 +2、+3 或其他内容?为什么需要这个?如果 +1/-1 被删除会怎样?
  • +1/-1 偏移了 MemoryHeader 的大小,因为它们被添加到 MemoryHeader* 中,这就是指针算法的工作原理。在 +1 的情况下,他有一个 MemoryHeader* 并且他想要获取对象的地址。在 -1 的情况下,他有对象的地址,他想获得 MemoryHeader*。
  • 这是假设您在为对象分配的每个内存块之前都有一个 MemoryHeader。
【解决方案2】:

operator delete 是一个释放函数

删除表达式首先调用析构函数,然后是相关的释放函数。

“Pool::memory()->save_destructor() 只是保存一个指向运行泛型析构函数的函数的指针。”是不好的设计。相反,如果要分担解除分配责任,则使用 std::shared_ptr 在更高级别处理该责任(您可以将类限制为只能通过生成 std::shared_ptr 的函数进行实例化)。这就是std::shared_ptr 的用途。

由于分配器负责销毁对象,因此将高低级别的关注点混为一谈,这会导致复杂性并可能导致错误。

将这些问题分开,例如通过使用std::shared_ptr,您可以获得更简单、更可靠的设计,这也将更符合C++语言中的这种分离。


Andrei Alexandrescu 的经典著作《现代 C++ 设计》详细讨论了如何实现自定义分配方案。您可能会在 Loki 库中找到该分配器。我相信它是开源的。


如果您想要类似 Java 的自动垃圾收集,那么您可以使用 Boehm 收集器。我知道有些人已经在大中型项目上成功地使用了它。但是,我没有直接的经验,但请检查一下是否是您的目标。

【讨论】:

    【解决方案3】:

    如果您想避免自动调用析构函数,则必须实现placement new 运算符,以防必须显式调用哪个析构函数(请参阅C++ FAQ)。

    如果您想通过delete 语句禁用释放内存,重载它是一个好主意,无论是全局(如果您知道自己在做什么)或每个类(可能使用继承。new/@987654325 @ 运算符是继承的)。

    这完全取决于您想要达到什么目标,以及您必须保持与现有系统的何种兼容性。

    无论哪种方式 - 它都是可行的。另一个问题是 - 应该这样做。

    【讨论】:

      【解决方案4】:

      您可能考虑的另一种方法是禁用相关类的delete

      只需将析构函数设为protectedprivate,并将池分配器添加为friend

      强制执行现有设计,而不是通过增加更多复杂性来支持不正确的使用(另一个答案),或者更改设计更简单和干净的东西(我的另一个答案)。

      【讨论】:

      • 这大概是他应该做的吧。为了使他的系统正常工作,1) 析构函数必须使得它们不必立即被调用(例如,没有 RAII),以及 2) 当内存实际被释放时,他需要能够以某种方式调用它们.为了让后者工作,他几乎肯定需要他的所有类都从一个共同的基础派生。所以真正的解决方案是在那个基础上定义operator newoperator delete;对他的课程一无所知的第三方图书馆将继续正常工作。
      猜你喜欢
      • 2017-11-11
      • 1970-01-01
      • 2011-02-23
      • 1970-01-01
      • 1970-01-01
      • 2013-05-31
      • 1970-01-01
      • 2019-03-08
      • 2016-12-24
      相关资源
      最近更新 更多