Перегрузка операторов new и delete. Павел Синай — различия между версиями

Материал из SEWiki
Перейти к: навигация, поиск
(Новая страница: «== Для чего это нужно == * Организация кэширования памяти. * Организация сборщика мусора. * По…»)
 
(Возможные проблемы)
 
(не показано 10 промежуточных версий 2 участников)
Строка 3: Строка 3:
 
* Организация сборщика мусора.
 
* Организация сборщика мусора.
 
* Поиск утечек памяти.
 
* Поиск утечек памяти.
 +
 +
== Перегрузка операторов new и delete в классах ==
 +
Как это делается:
 +
 +
<source lang="cpp">
 +
struct A
 +
{
 +
  int a;
 +
 +
  void * operator new(size_t size)
 +
  {
 +
      return malloc(size);
 +
  }
 +
 +
  void operator delete(void *p)
 +
  {
 +
      free(p);
 +
  }
 +
};
 +
</source>
 +
Эти операторы являются '''static'''.
 +
 +
В оператор new передается параметр size, указывающий сколько памяти нужно выделить. Это сделано для возможности наследования от нашего класса - наследникам скорее всего понадобится больше памяти, и мы должны ее выделить.
 +
Возвращаемое значение оператора new '''void*''', а не '''A*''', так как на момент его работы объект еще не существует. Конструктор будет вызван только после успешной отработки оператора new.
 +
 +
Перегрузка операторов для создания и удаления массива объектов производится отдельно:
 +
 +
<source lang="cpp">
 +
void * operator new[](size_t size)
 +
{
 +
  return malloc(size);
 +
}
 +
 +
void operator delete[](void *p)
 +
{
 +
  free(p);
 +
}
 +
</source>
 +
 +
В оператор new передается размер блока памяти, предназначенного для хранения '''всех''' объектов в массиве. После выполнения оператора new[] производится последовательный вызов конструкторов для объектов из массива.
 +
Перед выполнением оператора delete[] производится последовательный вызов деструкторов для всех объектов.
 +
 +
== Возможные проблемы ==
 +
<source lang="cpp">
 +
struct B : A
 +
{
 +
  int b;
 +
};
 +
 +
B *pb = new B[10];
 +
A *pa = pb;        //Ошибки не будет
 +
 +
delete [] pa;      //Ошибка - пытаемся вызвать 10 раз деструктор
 +
                  // для объекта класса A, но там объект класса B.
 +
pa++;              //Ошибка - указатель сдвинется на размер класса A,
 +
                  // но поскольку в массиве лежат объекты класса B
 +
                  // другого размера pa будет указывать не на
 +
                  // следующий объект, а непонятно куда.
 +
</source>
 +
 +
== Перегрузка глобальных операторов new и delete ==
 +
Если перегрузить операторы вне класса, то будет произведена перегрузка всех соответствующих операторов, находящихся как внутри классов так и глобальных.
 +
 +
<source lang="cpp">
 +
void * operator new(size_t size)
 +
{
 +
  return malloc(size);
 +
}
 +
 +
void operator delete(void *p)
 +
{
 +
  free(p);
 +
}
 +
</source>
 +
 +
Для перегрузки глобальных операторов также нужно перегружать отдельно операторы new и new[], delete и delete[].
 +
 +
== Дополнительные параметры оператора new ==
 +
Обычно, при ошибке во время выполнения оператора new кидается исключение. Можно сделать так, чтобы исключение не кидалось, а вместо этого оператор возвращал NULL:
 +
<source lang="cpp">
 +
int *pi = new(std::no_throw) int;
 +
</source>
 +
 +
Для оператора delete такой возможности не предусмотрено, так как он в любом случае не кидает исключений.
 +
 +
Кроме того, можно создавать объект на уже выделенной памяти:
 +
<source lang="cpp">
 +
int *A = new(pi)A();
 +
</source>
 +
Эту операцию нужно производить осторожно - размера выделенной заранее памяти должно хватить для размещения объекта A.
 +
В данном примере класс A содержит единственное поле int a, так что места для него будет достаточно.
 +
 +
Возможные применения:
 +
* Работа при сильно ограниченной памяти.
 +
* Необходимость создать объект по строго определенному адресу в памяти (например при работе со встраиваемой системой, если нужно создать объект для управления неким аппаратным ресурсом, расположенном по известному адресу).
 +
* Реализация паттерна "Феникс".

Текущая версия на 03:51, 11 июня 2012

Для чего это нужно

  • Организация кэширования памяти.
  • Организация сборщика мусора.
  • Поиск утечек памяти.

Перегрузка операторов new и delete в классах

Как это делается:

struct A
{
   int a;

   void * operator new(size_t size)
   {
      return malloc(size);
   }

   void operator delete(void *p)
   {
      free(p);
   }
};

Эти операторы являются static.

В оператор new передается параметр size, указывающий сколько памяти нужно выделить. Это сделано для возможности наследования от нашего класса - наследникам скорее всего понадобится больше памяти, и мы должны ее выделить. Возвращаемое значение оператора new void*, а не A*, так как на момент его работы объект еще не существует. Конструктор будет вызван только после успешной отработки оператора new.

Перегрузка операторов для создания и удаления массива объектов производится отдельно:

void * operator new[](size_t size)
{
   return malloc(size);
}

void operator delete[](void *p)
{
   free(p);
}

В оператор new передается размер блока памяти, предназначенного для хранения всех объектов в массиве. После выполнения оператора new[] производится последовательный вызов конструкторов для объектов из массива. Перед выполнением оператора delete[] производится последовательный вызов деструкторов для всех объектов.

Возможные проблемы

struct B : A
{
   int b;
};

B *pb = new B[10];
A *pa = pb;         //Ошибки не будет

delete [] pa;      //Ошибка - пытаемся вызвать 10 раз деструктор 
                   // для объекта класса A, но там объект класса B.
pa++;              //Ошибка - указатель сдвинется на размер класса A, 
                   // но поскольку в массиве лежат объекты класса B 
                   // другого размера pa будет указывать не на 
                   // следующий объект, а непонятно куда.

Перегрузка глобальных операторов new и delete

Если перегрузить операторы вне класса, то будет произведена перегрузка всех соответствующих операторов, находящихся как внутри классов так и глобальных.

void * operator new(size_t size)
{
   return malloc(size);
}

void operator delete(void *p)
{
   free(p);
}

Для перегрузки глобальных операторов также нужно перегружать отдельно операторы new и new[], delete и delete[].

Дополнительные параметры оператора new

Обычно, при ошибке во время выполнения оператора new кидается исключение. Можно сделать так, чтобы исключение не кидалось, а вместо этого оператор возвращал NULL:

int *pi = new(std::no_throw) int;

Для оператора delete такой возможности не предусмотрено, так как он в любом случае не кидает исключений.

Кроме того, можно создавать объект на уже выделенной памяти:

int *A = new(pi)A();

Эту операцию нужно производить осторожно - размера выделенной заранее памяти должно хватить для размещения объекта A. В данном примере класс A содержит единственное поле int a, так что места для него будет достаточно.

Возможные применения:

  • Работа при сильно ограниченной памяти.
  • Необходимость создать объект по строго определенному адресу в памяти (например при работе со встраиваемой системой, если нужно создать объект для управления неким аппаратным ресурсом, расположенном по известному адресу).
  • Реализация паттерна "Феникс".