【问题标题】:Why does operator delete's signature take two parameters?为什么 operator delete 的签名要带两个参数?
【发布时间】:2011-12-11 01:49:14
【问题描述】:

我一直在阅读有关重载 new 和 delete(以及放置 new/delete 等相关主题)的内容。到目前为止让我感到困惑的一件事是 operator delete 的标准签名是(在类范围内):

void operator delete(void *rawMemory, std::size_t size) throw();

删除是这样调用的:

MyClass* ptr = new MyClass;
delete ptr;

那么,delete ptr; 如何提供大小的第二个参数?另外,我可以假设 MyClass* 在这种情况下被隐式转换为 void* 吗?

【问题讨论】:

  • 我不确定第一部分,但是是的,所有指针都可以隐式转换为void *
  • @Shahbaz:是的,但请注意,您不能转换回MyClass*(这可能是 OP 想要做的)。对象在调用operator delete时已经被销毁,因此尝试访问它是无效的。
  • @MikeSeymour,当然不。就像我说的,它们可以被转换为void *,而不是相反。正如 OP 所问:我可以假设 MyClass* 在这种情况下被隐式转换为 void* 吗?

标签: c++ memory


【解决方案1】:

简答:

newdelete 运算符在类范围内被重载,以优化特定类对象的分配。但是由于 Inheritance 这样的某些野兽,可能会出现特殊情况,这可能导致分配请求超过类大小本身,因为 newdelete 重载的目的是针对大小为 @987654327 的对象进行特殊调整@,没有更大或更小,这些重载的运算符应该将所有其他wrong sized 内存请求转发到::operator new::operator delete,为了能够这样做,需要将大小参数作为参数传递。

长答案:

考虑一个特殊场景:

class Base 
{
    public:
        static void * operator new(std::size_t size) throw(std::bad_alloc);
};



class Derived: public Base 
{
   //Derived doesn't declare operator new
};                                  

int main()
{
    // This calls Base::operator new!
    Derived *p = new Derived;                 

    return 0;
}

在上面的示例中,由于继承了派生类Derived,继承了Base类的new运算符。这使得在基类中调用 operator new 为派生类的对象分配内存成为可能。我们的 operator new 处理这种情况的最佳方法是将此类请求“错误”内存量的调用转移到标准 operator new,如下所示:

void * Base::operator new(std::size_t size) throw(std::bad_alloc)
{

    if (size != sizeof(Base))          // if size is "wrong," i.e != sizeof Base class
    {
         return ::operator new(size);  // let std::new handle this request
    }
    else
    {
         //Our implementation
    }
}

在重载delete 运算符时,还必须确保由于特定类的运算符 new 将“错误”大小的请求转发到 ::operator new,因此必须将“错误大小”的删除请求转发到 ::operator delete,由于原始运营商保证以符合标准的方式处理这些请求。

所以自定义的delete 运算符将是这样的:

class Base 
{                            
   public:                                 
      //Same as before
      static void * operator new(std::size_t size) throw(std::bad_alloc);       
      //delete declaration
      static void operator delete(void *rawMemory, std::size_t size) throw();      

     void Base::operator delete(void *rawMemory, std::size_t size) throw()
     {
         if (rawMemory == 0) 
         {
              return;                            // No-Op is null pointer
         }

         if (size != sizeof(Base)) 
         {           
             // if size is "wrong,"
             ::operator delete(rawMemory);      //delegate to std::delete
             return;                            
         }
        //If we reach here means we have correct sized pointer for deallocation
        //deallocate the memory pointed to by rawMemory;

        return;
     }
};

进一步阅读:
以下 C++-Faq 条目讨论了以符合标准的方式重载 new 和 delete ,可能适合您阅读:

How should i write iso c++ standard conformant custom new and delete operators?

【讨论】:

    【解决方案2】:

    那么,如何删除ptr;提供大小的第二个参数?

    如果指针类型是具有虚拟析构函数的类类型,则来自有关对象类型的动态信息。如果它没有虚拟析构函数并且指针类型匹配指针类型 - 来自关于类型大小的编译时信息。否则delete ptr 是未定义的行为。

    【讨论】:

      【解决方案3】:

      当您调用delete 时,记住要释放的大小是编译器的工作。对于delete ptr;,它将传递sizeof(MyClass),对于delete[] ptr;,它必须记住数组中MyClass的数量,并传递正确的大小。我完全不知道这种语言的古怪角落是如何形成的。我想不出编译器必须为您记住运行时值的语言的任何其他部分。

      【讨论】:

      • 大概是因为它可以与 POD 类型一起使用,而无需它们都必须成为类 - 就像在 Java 中一样?
      • 我看不出 POD 是如何相关的。基本上,delete[] 证明编译器会跟踪动态数组的大小,但不会告诉我们该值。我们必须在内存中使用 additional 变量来跟踪 我们的 副本的数组大小。
      • 我的意思是它需要做魔术才能为 POD 添加 sizeof() 但是是的,它跟踪数组大小并且不会告诉你是愚蠢的 - 就像'c ' malloc 不会告诉你一个 alloc 有多大。这也很愚蠢,因为它知道数组有多大,所以数组和单个对象需要不同的语法。
      • 据我所知,一些编译器(例如 MSVC)并不总是跟踪数组大小以进行免费存储分配。当 MyClass 有一个微不足道的析构函数或没有析构函数时,不需要跟踪要调用多少个析构函数,因此没有分配数组头来存储大小。添加让程序员查询从空闲存储区分配的数组大小的功能可能需要更改 ABI,而且它通常会使用比当前行为更多的内存。
      • @bk1e:我想信息仍然在某个地方,我无法想象堆会如何工作。如果信息在那里,它不会占用更多的内存。现在肯定需要 ABI 更改才能添加它,但是为什么他们在制作operator new 时会做出这个决定?
      猜你喜欢
      • 1970-01-01
      • 2011-08-30
      • 1970-01-01
      • 1970-01-01
      • 2018-07-30
      • 2010-09-23
      • 1970-01-01
      相关资源
      最近更新 更多