C++放置new、继承和析构函数

4

各位朋友们,

在使用类继承时,如果使用placement-new,则需要基类进行内存释放。否则,在一个已经被释放的对象上调用基类的析构函数。 我希望能够从派生类中执行内存释放操作。因此,我希望得到一些想法和建议!(注:我不使用placement-new也可以,但我想自定义内存管理而不是使用new/delete)。

以下是一个示例代码片段:

#include <cstdint>
#include <cstdio>
#include <new>

class CParent
{
public :
    CParent() {
        printf("CParent()\n");
    }

    virtual ~CParent() {
        printf("~CParent()\n");
    }
};

class CAllocator
{
private :
    void Free(uint8_t *buffer) {
        printf("CAllocator::Free(%p)\n", buffer);
        delete [] buffer;
    }

    class CChild : public CParent
    {
    public :
        CChild(CAllocator &allocator, uint8_t *buffer)
            : mAllocator(allocator), mBuffer(buffer)
        {
            printf("CChild()\n");
        }

        ~CChild() {
            printf("~CChild()\n");
            mAllocator.Free(mBuffer);
        }

    private :
        CAllocator &mAllocator;
        uint8_t *mBuffer;
    };

public :
    CParent *Alloc() {
        uint8_t *buffer = new uint8_t[sizeof(CChild)];
        printf("CAllocator::Alloc() = %p\n", buffer);
        return new (buffer) CChild(*this, buffer);
    }
};

int main()
{
    CAllocator allocator;
    CParent *object = allocator.Alloc();

    // NB: Can't do `delete object` here because of placement-new
    object->~CParent();
    return 0;
}

这将产生以下输出:

CAllocator::Alloc() = 0x2001010
CParent()
CChild()
~CChild()
CAllocator::Free(0x2001010)
~CParent()

所以,~CParent() 在内存被释放后被调用...感谢您的帮助!

1
不要在析构函数内调用mAllocator.Free(mBuffer);。你不是在构造函数中分配了那块内存吗?那么对于释放操作,你应该在析构函数调用完成后再进行。 - Praetorian
这可能会有所帮助:http://en.cppreference.com/w/cpp/memory/new/operator_new - R Sahu
1个回答

3
您把以下概念混淆在一起,让我觉得您对它们的定义不够清晰:
1. 基类/派生类的析构函数。 2. 放置 new 运算符。 3. 内存分配和释放。
当您使用普通的 operator new 来分配对象时,会发生两件事情:
1. 为对象分配内存。 2. 调用对象的构造函数(对于有构造函数的类)。
当您调用 operator delete 来删除由 operator new 返回的指针时,会发生两件事情:
1. 调用对象的析构函数。 2. 释放内存。
当您使用放置 new 运算符时,需要:
1. 在调用放置 new 运算符之前分配内存。 2. 在调用 new 时使用预先分配的内存。会调用类的构造函数来初始化对象。
对于这样的对象,您需要:
1. 显式调用析构函数。 2. 使用与内存分配方式相匹配的方法来释放内存。如果您使用 operator new char[size]; 分配内存,请使用 delete [] ptr; 释放内存。如果您使用 malloc(size) 分配内存,请使用 free(ptr) 释放内存。
为了使您的代码更加清晰,您应该分离:
1. 内存分配和释放的责任。 2. 调用构造函数和析构函数的责任。
在您发布的代码中,类 CChild 看起来不够干净。它是否是一个面向用户的类还是一个帮助您管理内存的辅助类并不清楚。
如果您打算将其作为面向用户的类,请重构代码:
#include <cstdint>
#include <cstdio>
#include <new>

class CParent
{
   public :
      CParent() {
         printf("CParent()\n");
      }

      virtual ~CParent() {
         printf("~CParent()\n");
      }
};

class CChild : public CParent
{
   public :
      CChild()
      {
         printf("CChild()\n");
      }

      ~CChild() {
         printf("~CChild()\n");
      }

   private :
};

class CAllocator
{
   public :
      void Free(uint8_t *buffer) {
         printf("CAllocator::Free(%p)\n", buffer);
         delete [] buffer;
      }


      uint8_t *Alloc(size_t size) {
         uint8_t *buffer = new uint8_t[size];
         printf("CAllocator::Alloc() = %p\n", buffer);
         return buffer;
      }
};

int main()
{
   CAllocator allocator;
   uint8_t *buffer = allocator.Alloc(sizeof(CChild));

   CParent* object = new (buffer) CChild;

   object->~CParent();

   allocator.Free(buffer);

   return 0;
}

如果您想将CChild用作管理内存的辅助类,首先要做的是确保CAllocator :: Alloc()和CAlloctor :: Free()是对称的。由于Alloc()返回指向CParent的指针,因此您需要更改Free()以接受指向CParent的指针并正确处理它。以下是我认为代码应该如何编写:
#include <cstdint>
#include <cstdio>
#include <new>

class CParent
{
   public :
      CParent() {
         printf("CParent()\n");
      }

      virtual ~CParent() {
         printf("~CParent()\n");
      }
};

class CAllocator
{
   private :

      class CChild : public CParent
      {
         public :
            CChild(uint8_t *buffer) : mBuffer(buffer)
            {
               printf("CChild()\n");
            }

            ~CChild() {
               printf("~CChild()\n");

               // The object has ownership of the buffer.
               // It can deallocate it.
               delete [] mBuffer;
            }

         private :
            uint8_t *mBuffer;
      };

   public :

      // Make Alloc and Free symmetric.
      // If Alloc() returns a CParent*, make sure Free()
      // accepts the same value and does the right thing
      // with it.

      CParent *Alloc() {
         uint8_t *buffer = new uint8_t[sizeof(CChild)];
         printf("CAllocator::Alloc() = %p\n", buffer);

         // Transfer the ownership of buffer to CChild
         return new (buffer) CChild(buffer);
      }

      void Free(CParent* object) {
         printf("CAllocator::Free(%p)\n", object);
         object->~CParent();
      }


};

int main()
{
   CAllocator allocator;

   CParent *object = allocator.Alloc();
   allocator.Free(object);

   return 0;
}

嗨,Sahu。感谢您的建议。在您的第二个例子中,当内存被释放后,~CParent() 仍然会被调用。然而,我认为我的第一个例子中缺乏对称性,这使得事情变得更加复杂。如果内存是在 CAllocator 中分配的,那么它也应该在 CAllocator 中释放。我认为您的第二个例子可以稍微修改一下以使其正常工作:在调用 object->~CParent() 后,内存可以在 CAllocator::Free() 中释放,而不是在 ~CChild() 中。我会尝试一下。 - user4921182
object->~CParent() 将调用 ~CChild(),在执行 ~CChild() 函数体中的代码之前,会先调用 ~CParent()。因此,在内存被释放之前,~CParent() 将被调用,而不是在之后。 - R Sahu
@Sahu:你试过了吗?我试过了,~CChild()的主体肯定在~CParent()之前执行。我相信这就是C++的工作原理。我还尝试了你第二个示例的稍微修改版本,它完美地运行了!谢谢! - user4921182
@seeker6,你是对的。我刚才有点困惑。 - R Sahu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接