【问题标题】:C++ placement new, inheritance and destructorC++放置new、继承和析构函数
【发布时间】:2015-05-20 15:47:32
【问题描述】:

同学们,

在类层次结构上使用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()在内存被释放后被调用... 非常感谢您的帮助!

【问题讨论】:

  • 好吧,不要在析构函数中调用mAllocator.Free(mBuffer);。您不是从构造函数中分配内存,是吗?然后你应该对释放做同样的事情,在析构函数调用完成后调用它。

标签: c++ placement-new


【解决方案1】:

您以这样的方式混合以下概念,这让我认为您不清楚它们应该是什么:

  1. 基类/派生类析构函数。
  2. 放置new操作员。
  3. 内存分配和释放。

当您使用普通的旧 operator new 分配对象时,会发生两件事:

  1. 为对象分配内存。
  2. 对象的构造函数(对于具有构造函数的类)被调用。

当您在 operator new 返回的指针上调用 operator delete 时,会发生两件事:

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

【讨论】:

  • 嗨萨胡。谢谢您的意见。在您的第二个示例中,~CParent() 在内存被释放后仍将被调用。然而,我认为我的第一个示例中缺乏对称性会使事情复杂化。如果内存在CAllocator 中分配,它也应该在CAllocator 中释放。我认为您的第二个示例可以稍作修改以使其工作:在调用object-&gt;~CParent() 而不是~CChild() 之后,可以在CAllocator::Free() 中释放内存。我试试看。
  • object-&gt;~CParent() 将调用~CChild(),它会在执行~CChild() 正文中的行之前首先调用~CParent()。因此,~CParent() 将被调用之前内存被释放,不是之后
  • @Sahu:你试过了吗?我试过了,~CChild() 的主体肯定会在~CParent() 之前执行。我相信这就是 C++ 的工作方式。我还尝试了您的第二个示例的略微修改版本,它确实工作得很好!谢谢!
  • @seeker6,你是对的。我一时糊涂了。
猜你喜欢
  • 2018-01-17
  • 2012-11-14
  • 2012-04-28
  • 2011-12-31
  • 2011-10-14
  • 2013-02-16
  • 2011-12-27
  • 1970-01-01
  • 2021-04-13
相关资源
最近更新 更多