动态对象创建(new,delete)

动态对象创建(new,delete)

一、相关日志

C++编程技巧

http://blog.163.com/zhoumhan_0351/blog/static/399542272010018101213512

C++基础笔记(一)

http://blog.163.com/zhoumhan_0351/blog/static/39954227201012465955824

二、动态对象创建

当创键一个C++对象时:

1)分配内存

静态存储区,{},堆

2)调用构造函数进行初始化

malloc()只是分配了一块内存,而不是生成一个对象,所以它返回了一个void*类型指针。

1、C++把创建一个对象所需的所有动作都结合在一个称为new的运算符里,而用delete来销毁一个对象(先调用析构函数,再释放内存)。

大多数默认的new和delete实现机制都使用了malloc()和free()。建议删除指针后把指针赋为NULL或0,这样就不会删除两次

定义一个友元函数为内联函数,不会改变其友元状态,而且它仍是全局函数而不是一个类的成员函数。

如果要对void*类型指针进行delete操作,可能发生错误,因为它将不执行析构函数。

void** st = new void*[quantity + increase];

the type of object allocated is a void*, so the expression allocates an array of 

void pointers.

如下为一个小程序:

//: C13:PStash.h

// Holds pointers instead of objects

#ifndef PSTASH_H

#define PSTASH_H

class PStash {

public:

  int quantity; // Number of storage spaces

  int next; // Next empty space

   // Pointer storage:

  void** storage;

  void inflate(int increase);

public:

  PStash() : quantity(0), storage(0), next(0) {}

  ~PStash();

  int add(void* element);

  void* operator[](int index) const; // Fetch

  // Remove the reference from this PStash:

  void* remove(int index);

  // Number of elements in Stash:

  int count() const { return next; }

};

#endif // PSTASH_H ///:~

//: C13:PStash.cpp {O}

// Pointer Stash definitions

//#include "PStash.h"

#include <iostream>

#include <cstring> // 'mem' functions

using namespace std;

int PStash::add(void* element) {

  const int inflateSize = 10;

  if(next >= quantity)

inflate(inflateSize);

  storage[next++] = element;

  return(next - 1); // Index number

}

// No ownership:

PStash::~PStash() {

  for(int i = 0; i < next; i++)

  delete []storage; 

}

// Operator overloading replacement for fetch

void* PStash::operator[](int index) const {

  if(index >= next)

return 0; // To indicate the end

  // Produce pointer to desired element:

  return storage[index];

}

void* PStash::remove(int index) {

  void* v = operator[](index);

  // "Remove" the pointer:

  if(v != 0) storage[index] = 0;

  return v;

}

void PStash::inflate(int increase) {

  const int psz = sizeof(void*);

  void** st = new void*[quantity + increase];

  memset(st, 0, (quantity + increase) * psz);

  memcpy(st, storage, quantity * psz);

  quantity += increase;

  delete []storage; // Old storage

  storage = st; // Point to new memory

} ///:~

//: C13:PStashTest.cpp

//{L} PStash

// Test of pointer Stash

//#include "PStash.h"

#include <iostream>

using namespace std;

int main() {

  PStash intStash;

  // 'new' works with built-in types, too. Note

  // the "pseudo-constructor" syntax:

  for(int i = 0; i < 25; i++)

intStash.add(new int(i));

  for(int j = 0; j < intStash.count(); j++)

cout << "intStash[" << j << "] = "

 << *(int*)intStash[j] << endl;

  // Clean up:

  for(int k = 0; k < intStash.count(); k++)

delete intStash.remove(k);

  return 1;

} ///:~

由上我们可以看出,可以将一个非void*类型的指针赋给void*型,但是不可以将一个void*型指针赋给一个非void*指针。

我们注意到:
 delete intStash.remove(k);由于是对整形删除,int没有构造函数,所以这样可以,如果存放的是string,则需要
 delete (string*)intStash.remove(k);来转换。
可以用模板来解决这类问题。

2、如果new找不到足够的内存来分配,则一个new-handle的函数将被调用。首先检查指向的函数的指针,如果非0,则它指向的函数被调用。

new-handle的默认动作是产生一个异常。包含new.h来替换new-handle,再以想装入的函数地址为参数调用set_new_handler()函数。

//: C13:NewHandler.cpp

// Changing the new-handler

#include <iostream>

#include <cstdlib>

#include <new>

using namespace std;

int count = 0;

void out_of_memory() {

  cerr << "memory exhausted after " << count 

<< " allocations!" << endl;

  exit(1);

}

int main() {

  set_new_handler(out_of_memory);

  while(1) {

count++;

new int[1000]; // Exhausts memory

  }

} ///:~

当然了,可以在new-handler中写回收内存等相关程序。

3、重载new和delete

当我们调用new表达式时,做两件事:使用operator new()分配内存,然后调用构造函数。在delete中调用构造函数,然后用operator delete()释放内存。C+允许重载new和delete来实现我们自己的存储方案。

当重载operator new()与operator delete()时,我们只是改变了原来的内存分配方法。

1)重载全局的new,delete

operator new()的返回值是一个void*,所做的事是分配内存。operator delete()参数是一个指向由operator new()分配的内存的void*。参数是void*是因为它是调用析构函数后得到的指针。一般格式如下例子如示:

 //: C13:GlobalOperatorNew.cpp

// Overload global new/delete

#include <cstdio>

#include <cstdlib>

using namespace std;

void* operator new(size_t sz) {

  printf("operator new: %d Bytes\n", sz);

  void* m = malloc(sz);

  if(!m) puts("out of memory");

  return m;

}

void operator delete(void* m) {

  puts("operator delete");

  free(m);

}

class S {

  int i[100];

public:

  S() { puts("S::S()"); }

  ~S() { puts("S::~S()"); }

};

int main() {

  puts("creating & destroying an int");

  int* p = new int(47);

  delete p;

  puts("creating & destroying an s");

  S* s = new S;

  delete s;

  puts("creating & destroying S[3]");

  S* sa = new S[3];

  delete []sa;

} ///:~

由上通用形式的例子中,我们可以看出,new和delete其实是用了malloc和free来分配释放内存。我们这里用printf和puts而不用iostream是因为创建iostream对象时(如全局的cin,cout等)它们调用new去分配内存,这样可能形式死锁,而用printf就不会出现这个情况,因为它不用new来初始化。

此外,我们还看到,数组对象是一个个创建,并调用构造函数和析构函数的。new操作符需要一个size_t来指定分配内存的长度。

2)对于一个类重载new和delete

为一个类重载new和delete时,尽管不必显式的使用static,但实际上仍是在创建static成员函数。当编译器看到使用new创建自己定义的类的对象时,它选择成员版本的operator new,而不是全局版本的,但全局版本的new仍为其它没有定义operator new的类所使用。局部operator new与全局operator new有相同的语法。

#include <cstddef> // Size_t

#include <fstream>

#include <iostream>

#include <new>

using namespace std;

//ofstream out("C:\\Framis.out");

class Framis {

  enum { sz = 6 };

  char c[sz]; // To take up space, not used

  static unsigned char pool[];

  static bool alloc_map[];

public:

  enum { psize = 8 };  // frami allowed

  Framis() { cout << "Framis()\n"; }

  ~Framis() { cout << "~Framis() ... "; }

  void* operator new(size_t) throw(bad_alloc);

  void operator delete(void*);

};

unsigned char Framis::pool[psize * sizeof(Framis)];

bool Framis::alloc_map[psize] = {false};

// Size is ignored -- assume a Framis object

void* Framis::operator new(size_t) throw(bad_alloc) {

  for(int i = 0; i < psize; i++)

if(!alloc_map[i]) {

  cout << "using block " << i << " ... ";

  alloc_map[i] = true; // Mark it used

  return pool + (i * sizeof(Framis));

}

  cout << "out of memory" << endl;

  throw bad_alloc();

}

void Framis::operator delete(void* m) {

  if(!m) return; // Check for null pointer

  // Assume it was created in the pool

  // Calculate which block number it is:

  unsigned long block = (unsigned long)m - (unsigned long)pool;

  block /= sizeof(Framis);

  cout << "freeing block " << block << endl;

  // Mark it free:

  alloc_map[block] = false;

}

int main() {

  Framis* f[Framis::psize];

  try {

for(int i = 0; i < Framis::psize; i++)

  f[i] = new Framis;

new Framis; // Out of memory

  } 

  catch(bad_alloc) {

cerr << "Out of memory!" << endl;

  }

  delete f[5];

  f[1] = 0;

  // Use released memory:

  Framis* x = new Framis;

  delete x;

  for(int j = 0; j < Framis::psize; j++)

delete f[j]; // Delete f[10] OK

} ///:~

由上结果,我们可以印证前面的理论:new是先分配内存,后调用构造函数,而delete是先调用析构函数,再释放内存。当我们

delete f[5];

// Use released memory:

Framis* x = new Framis;

释放了内存,接着再分配时,便可以在释放的内存上重新使用,使用了如下语句:

if(!alloc_map[i]) {

  cout << "using block " << i << " ... ";

由于我们在程序中,设置了如果将预定的内存分配结束后,就打印out of memory,所以可以控制内存分配。

3)为数组重载new和delete

If you overload operator new and delete for a class, those operators are

 called whenever you create an object of that class. However, if you create an 

array of those class objects, the global operator new( ) is called to allocate 

enough storage for the array all at once, and the global operator delete( ) is 

called to release that storage. You can control the allocation of arrays of 

objects by overloading the special array versions of operator new[ ] and 

operator delete[ ] for the class. 

//: C13:ArrayOperatorNew.cpp

// Operator new for arrays

#include <new> // Size_t definition

#include <fstream>

#include "iostream"

using namespace std;

//ofstream trace("ArrayOperatorNew.out");

class Widget {

  enum { sz = 8 };

  int i[sz];

public:

  Widget() { cout << "*"; }

  ~Widget() { cout<< "~"; }

  void* operator new(size_t sz) {

cout << "Widget::new: "

 << sz << " bytes" << endl;

  return ::new char[sz];

  }

  void operator delete(void* p) {

cout << "Widget::delete" << endl;

::delete []p;

  }

  void* operator new[](size_t sz) {

cout << "Widget::new[]: "

 << sz << " bytes" << endl;

return ::new char[sz];

  }

  void operator delete[](void* p) {

cout << "Widget::delete[]" << endl;

::delete []p;

  }

};

int main() {

  cout << "new Widget" << endl;

  Widget* w = new Widget;

  cout << "\ndelete Widget" << endl;

  delete w;

  cout << "\nnew Widget[6]" << endl;

  Widget* wa = new Widget[6];

  cout << "\ndelete []Widget" << endl;

  delete []wa;

} ///:~

注意,上面的196=32*6+4,多出的4个字节是存储数组信息的。数据中的每一个元素都调用了默认的构造函数和析构函数。

4)构造函数的调用

如果内存分配不成功,构造函数不会调用。

   //: C13:NoMemory.cpp

// Constructor isn't called if new fails

#include <iostream>

#include <new> // bad_alloc definition

using namespace std;

class NoMemory {

public:

  NoMemory() {

cout << "NoMemory::NoMemory()" << endl;

  }

  void* operator new(size_t sz) throw(bad_alloc){

cout << "NoMemory::operator new" << endl;

throw bad_alloc(); // "Out of memory"

  }

};

int main() {

  NoMemory* nm = 0;

  try {

nm = new NoMemory;

  } 

   catch(bad_alloc) {

cerr << "Out of memory exception" << endl;

  }

  cout << "nm = " << nm << endl;

} ///:~

当new返回0时,编译器将告知产生一个bad_malloc。nm初始化为0很重要的,因为new表达式没有执行完毕,指针置为0可以确保我们没有误用它。

5)placement new和delete

重载new还有两个其它用途:

(1)在内存中的指定位置上放置一个对象。

(2)选择不同的内存分配方案。
Both of these situations are solved with the same mechanism: The 

overloaded operator new( ) can take more than one argument. the first 

argument is always the size of the object, which is secretly calculated and 

passed by the compiler. But the other arguments can be anything you 

want – the address you want the object placed at, a reference to a memory 

allocation function or object, or anything else that is convenient for you.
The way that you pass the extra arguments to operator new( ) during a 

call may seem slightly curious at first. You put the argument list (without the 

size_t argument, which is handled by the compiler) after the keyword new 

and before the class name of the object you’re creating. For example,
X* xp = new(a) X;

将a作为第二参数传递。

//: C13:PlacementOperatorNew.cpp

// Placement with operator new()

#include <cstddef> // Size_t

#include <iostream>

using namespace std;

class X {

  int i;

public:

  X(int ii = 0) : i(ii) {

cout << "this = " << this << endl;

  }

  ~X() {

cout << "X::~X(): " << this << endl;

  }

  void* operator new(size_t, void* loc) {

return loc;

  }

};

int main() {

  int l[10];

  cout << "l = " << l+1 << endl;

  X* xp = new(l) X(47); // X at location l

  xp->X::~X(); // Explicit destructor call

  // ONLY use with placement!

} ///:~

注意,不能用动态存储机制来释放内存,因为内存不是在堆上根本的。我们用非常特殊的语法:

  xp->X::~X(); // Explicit destructor call

    There’s also a placement operator delete that is only called if a 

constructor for a placement new expression throws an exception (so that 

the memory is automatically cleaned up during the exception).

参考:

1、thinking in C++

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值