C++ delete 和 delete []

C++ delete 和 delete []

简单结论:

new delete
new [] delete []
 
文章
对 delete [] 的声明
void operator delete[] (void* ptr) throw (); void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) throw(); void operator delete[] (void* ptr, void* voidptr2) throw();
对new 的声明
void* operator new (std::size_t size) throw (std::bad_alloc);

void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw();

void* operator new (std::size_t size, void* ptr) throw();
--------------------------------------------------
使用相同形式的new和delete
 
下面这段代码有什么问题? 


std::string *stringArray = new std::string[100];
...
delete stringArray;


   每件事看起来都很正常。也为 new 搭配了一个 delete。但是,仍然有某件事情彻底错了。程序的行为是未定义的。直到最后,stringArray 指向的 100 个 string 对象中的 99 个不太可能被完全销毁,因为它们的析构函数或许根本没有被调用。

  当你使用了一个 new 表达式(也就是说,通过使用 new 动态创建一个对象),有两件事情会发生。首先,分配内存(通过一个被称为 operator new 的函数——参见 Item 49 和 51)。第二,一个或多个构造函数在这些内存上被调用。当你使用一个 delete 表达式(也就是说,使用 delete),有另外的两件事情会发生:一个或多个析构函数在这些内存上被调用,然后内存被回收(通过一个被称为 operator delete 的函数——参见 Item 51)。对于 delete 来说有一个大问题:在要被删除的内存中到底驻留有多少个对象?这个问题的答案将决定有多少个析构函数必须被调用。

  事实上,问题很简 单:将要被删除的指针是指向一个单一的对象还是一个对象的数组?这是一个关键的问题,因为单一对象的内存布局通常不同于数组的内存布局。详细地说,一个数 组的内存布局通常包含数组的大小,这样可以使得 delete 更容易知道有多少个析构函数需要被调用。而一个单一对象的内存中缺乏这个信息。你可以认为不同的内存布局看起来如下图,那个 n 就是数组的大小:


  这当然只是一个例子。编译器并不是必须这样实现,虽然很多是这样的。

  当你对一个指针使用 delete,delete 知道是否有数组大小信息的唯一方法就是由你来告诉它。如果你在你使用的 delete 中加入了方括号,delete 就假设那个指针指向的是一个数组。否则,就假设指向一个单一的对象。

std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = new std::string[100];
...
delete stringPtr1; // delete an object
delete [] stringPtr2; // delete an array of objects


   如果你对 stringPtr1 使用了 [] 形式会发生什么呢?结果是未定义的,但不太可能是什么好事。假设如上图的布局,delete 将读入某些内存的内容并将其看作一个数组的大小,然后开始调用那么多析构函数,不仅全然不顾它在其上工作的内存不是数组,而且还可能忘掉了它正忙着析构的 对象的类型。

  如果你对 stringPtr2 没有使用 [] 形式会发生什么呢?也是未定义的,只不过你不会看到它会引起过多的析构函数被调用。此外,对于类似 int 的内建类型其结果也是未定义的(而且有时是有害的),即使这样的类型没有析构函数。

  规则很简单。如果你在 new 表达式中使用了 [],你也必须在相应的 delete 表达式中使用 []。如果你在 new 表达式中没有使用 [],在匹配的 delete 表达式中也不要使用 []。

   当你写的一个类中包含一个指向动态分配的内存的指针,而且提供了多个构造函数的时候,这条规则尤其重要,应镌刻脑海,因为那时你必须小心地在所有的构造 函数中使用相同形式的 new 初始化那个指针成员。如果你不这样做,你怎么知道在你的析构函数中应该使用哪种形式的 delete 呢?

  这个规则对于有 typedef 倾向的人也很值得注目,因为这意味着一个 typedef 的作者必须在文档中记录:当用 new 生成一个 typedef 类型的对象时,应该使用哪种形式的 delete。例如,考虑这个 typedef:

typedef std::string AddressLines[4]; // a person’s address has 4 lines,
// each of which is a string


  因为 AddressLines 是一个数组,这里使用 new,

std::string *pal = new AddressLines; // note that "new AddressLines"
// returns a string*, just like
// "new string[4]" would


  必须用 delete 的数组形式进行匹配:

delete pal; // undefined!
delete [] pal; // fine


   为了避免这种混淆,要克制对数组类型使用 typedef。那很简单,因为标准 C++ 库(参见 Item 54)包含 string 和 vector,而且那些模板将对动态分配数组的需要减少到几乎为零。例如,这里,AddressLines 可以被定义为一个 string 的 vector,也就是说,类型为 vector。

  Things to Remember

  ·如果你在 new 表达式中使用了 [],你必须在对应的 delete 表达式中使用 []。如果你在 new 表达式中没有使用 [],你也不必在对应的 delete 表达式中不使用 []。

 
--------------------------------

人们有时好像喜欢故意使C++语言的术语难以理解。比如说new操作符(new operator)和operator new的区别。

  当你写这样的代码:

string *ps = new string("Memory Management");

   你使用的new是new操作符。这个操作符就象sizeof一样是语言内置的,你不能改变它的含义,它的功能总是一样的。它要完成的功能分成两部分。第 一部分是分配足够的内存以便容纳所需类型的对象。第二部分是它调用构造函数初始化内存中的对象。new操作符总是做这两件事情,你不能以任何方式改变它的 行为。

  你所能改变的是如何为对象分配内存。new操作符调用一个函数来完成必需的内存分配,你能够重写或重载这个函数来改变它的行为。new操作符为分配内存所调用函数的名字是operator new。

  函数operator new 通常这样声明:

void * operator new(size_t size);

   返回值类型是void*,因为这个函数返回一个未经处理(raw)的指针,未初始化的内存。(如果你喜欢,你能写一种operator new函数,在返回一个指针之前能够初始化内存以存储一些数值,但是一般不这么做。)参数size_t确定分配多少内存。你能增加额外的参数重载函数 operator new,但是第一个参数类型必须是size_t。(有关operator new更多的信息参见Effective C++ 条款8至条款10。)

  你一般不会直接调用operator new,但是一旦这么做,你可以象调用其它函数一样调用它:

void *rawMemory = operator new(sizeof(string));

  操作符operator new将返回一个指针,指向一块足够容纳一个string类型对象的内存。

  就象malloc一样,operator new的职责只是分配内存。它对构造函数一无所知。operator new所了解的是内存分配。把operator new 返回的未经处理的指针传递给一个对象是new操作符的工作。当你的编译器遇见这样的语句:

string *ps = new string("Memory Management");

  它生成的代码或多或少与下面的代码相似(更多的细节见Effective C++条款8和条款10,还有我的文章Counting object里的注释。):

void *memory = // 得到未经处理的内存
operator new(sizeof(string)); // 为String对象
call string::string("Memory Management") //初始化
on *memory; // 内存中

// 的对象

string *ps = // 是ps指针指向
static_cast(memory); // 新的对象

  注意第二步包含了构造函数的调用,你做为一个程序员被禁止这样去做。你的编译器则没有这个约束,它可以做它想做的一切。因此如果你想建立一个堆对象就必须用new操作符,不能直接调用构造函数来初始化对象。

  Placement new

   有时你确实想直接调用构造函数。在一个已存在的对象上调用构造函数是没有意义的,因为构造函数用来初始化对象,而一个对象仅仅能在给它初值时被初始化一 次。但是有时你有一些已经被分配但是尚未处理的的(raw)内存,你需要在这些内存中构造一个对象。你可以使用一个特殊的operator new ,它被称为placement new。

  下面的例子是placement new如何使用,考虑一下:

class Widget {
 public:
  Widget(int widgetSize);
  ...
};

Widget * constructWidgetInBuffer(void *buffer,

int widgetSize)
{
 return new (buffer) Widget(widgetSize);
}

   这个函数返回一个指针,指向一个Widget对象,对象在转递给函数的buffer里分配。当程序使用共享内存或memory-mapped I/O时这个函数可能有用,因为在这样程序里对象必须被放置在一个确定地址上或一块被例程分配的内存里。(参见条款4,一个如何使用placement new的一个不同例子。)

  在constructWidgetInBuffer里面,返回的表达式是:

new (buffer) Widget(widgetSize)

   这初看上去有些陌生,但是它是new操作符的一个用法,需要使用一个额外的变量(buffer),当new操作符隐含调用operator new函数时,把这个变量传递给它。被调用的operator new函数除了待有强制的参数size_t外,还必须接受void*指针参数,指向构造对象占用的内存空间。这个operator new就是placement new,它看上去象这样:

void * operator new(size_t, void *location)
{
 return location;
}

   这可能比你期望的要简单,但是这就是placement new需要做的事情。毕竟operator new的目的是为对象分配内存然后返回指向该内存的指针。在使用placement new的情况下,调用者已经获得了指向内存的指针,因为调用者知道对象应该放在哪里。placement new必须做的就是返回转递给它的指针。(没有用的(但是强制的)参数size_t没有名字,以防止编译器发出警告说它没有被使用;见条款6。) placement new是标准C++库的一部分。为了使用placement new,你必须使用语句#include (或者如果你的编译器还不支持这新风格的头文件名)。

   让我们从placement new回来片刻,看看new操作符(new operator)与operator new的关系,你想在堆上建立一个对象,应该用new操作符。它既分配内存又为对象调用构造函数。如果你仅仅想分配内存,就应该调用operator new函数;它不会调用构造函数。如果你想定制自己的在堆对象被建立时的内存分配过程,你应该写你自己的operator new函数,然后使用new操作符,new操作符会调用你定制的operator new。如果你想在一块已经获得指针的内存里建立一个对象,应该用placement new。

  Deletion and Memory Deallocation

  为了避免内存泄漏,每个动态内存分配必须与一个等同相反的deallocation对应。函数operator delete与delete操作符的关系与operator new与new操作符的关系一样。当你看到这些代码:

string *ps;
...
delete ps; // 使用delete 操作符

  你的编译器会生成代码来析构对象并释放对象占有的内存。

  Operator delete用来释放内存,它被这样声明:

void operator delete(void *memoryToBeDeallocated);

  因此, delete ps;

  导致编译器生成类似于这样的代码:

ps->~string(); // call the object's dtor
operator delete(ps); // deallocate the memory

// the object occupied

  这有一个隐含的意思是如果你只想处理未被初始化的内存,你应该绕过new和delete操作符,而调用operator new 获得内存和operator delete释放内存给系统:

void *buffer = // 分配足够的
operator new(50*sizeof(char)); // 内存以容纳50个char

//没有调用构造函数

...
operator delete(buffer); // 释放内存

// 没有调用析构函数

  这与在C中调用malloc和free等同。

   如果你用placement new在内存中建立对象,你应该避免在该内存中用delete操作符。因为delete操作符调用operator delete来释放内存,但是包含对象的内存最初不是被operator new分配的,placement new只是返回转递给它的指针。谁知道这个指针来自何方?而你应该显式调用对象的析构函数来解除构造函数的影响:

// 在共享内存中分配和释放内存的函数 void * mallocShared(size_t size);

void freeShared(void *memory);
void *sharedMemory = mallocShared(sizeof(Widget));
Widget *pw = // 如上所示,
constructWidgetInBuffer(sharedMemory, 10); // 使用

// placement new

...
delete pw; // 结果不确定! 共享内存来自
// mallocShared, 而不是operator new

pw->~Widget(); // 正确。 析构 pw指向的Widget,

// 但是没有释放
//包含Widget的内存

freeShared(pw); // 正确。 释放pw指向的共享内存

// 但是没有调用析构函数

  如上例所示,如果传递给placement new的raw内存是自己动态分配的(通过一些不常用的方法),如果你希望避免内存泄漏,你必须释放它。(参见我的文章Counting objects里面关于placement delete的注释。)

  Arrays

  到目前为止一切顺利,但是还得接着走。到目前为止我们所测试的都是一次建立一个对象。怎样分配数组?会发生什么?

string *ps = new string[10]; // allocate an array of
// objects

   被使用的new仍然是new操作符,但是建立数组时new操作符的行为与单个对象建立有少许不同。第一是内存不再用operator new分配,代替以等同的数组分配函数,叫做operator new[](经常被称为array new)。它与operator new一样能被重载。这就允许你控制数组的内存分配,就象你能控制单个对象内存分配一样(但是有一些限制性说明,参见Effective C++ 条款8)。

  (operator new[]对于C++来说是一个比较新的东西,所以你的编译器可能不支持它。如果它不支持,无论在数组中的对象类型是什么,全局operator new将被用来给每个数组分配内存。在这样的编译器下定制数组内存分配是困难的,因为它需要重写全局operator new。这可不是一个能轻易接受的任务。缺省情况下,全局operator new处理程序中所有的动态内存分配,所以它行为的任何改变都将有深入和普遍的影响。而且全局operator new有一个正常的签名(normal signature)(也就单一的参数size_t,参见Effective C++条款9),所以如果你 决定用自己的方法声明它,你立刻使你的程序与其它库不兼容基于这些考虑,在缺乏operator new[]支持的编译器里为数组定制内存管理不是一个合理的设计。)

  第二个不同是new操作符调用构造函数的数量。对于数组,在数组里的每一个对象的构造函数都必须被调用:

string *ps = // 调用operator new[]为10个
new string[10]; // string对象分配内存,

// 然后对每个数组元素调用
// string对象的缺省构造函数。

  同样当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operator delete来释放内存。

  就象你能替换或重载operator delete一样,你也替换或重载operator delete[]。在它们重载的方法上有一些限制。请参考优秀的C++教材。

   new和delete操作符是内置的,其行为不受你的控制,凡是它们调用的内存分配和释放函数则可以控制。当你想定制new和delete操作符的行为 时,请记住你不能真的做到这一点。你只能改变它们为完成它们的功能所采取的方法,而它们所完成的功能则被语言固定下来,不能改变。(You can modify how they do what they do, but what they do is fixed by the language)
原文地址:https://www.cnblogs.com/timssd/p/4781102.html