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

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

Перед изучением данной темы рекомендуется ознакомиться со следующей темой:


Содержание



1. В каких случаях нужно перегружать операторы new и delete?

Оператор new нужно перегружать в случаях, если память выделяется по особому (нестандартным способом). Соответственно, оператор delete должен освобождать эту память нестандартным способом. Как правило, если в классе перегружается оператор new, то в этом классе также перегружается оператор delete.

 

2. Какими способами можно перегружать операторы new и delete?

Операторы new и delete могут быть перегружены двумя способами. Эти способы отличаются объявлением операторной функции:

  • перегрузка операторов new и delete с помощью операторной функции, которая реализована в пределах класса. Это есть способ перегрузки new и delete для конкретного класса. При вызове new и delete для этого класса будут вызваны перегруженные операторные функции, которые выделяют/освобождают память специфическим образом. При вызове операторов new и delete для других классов используются глобальные операторы new и delete;
  • глобальная перегрузка операторов new и delete. В этом случае операторы new и delete перегружаются для встроенных типов (float, int и т.д.).

 

3. Общая форма перегрузки оператора new

Общая форма перегрузки оператора new имеет следующий вид:

return_type * operator new(size_t size)
{
    // Выделение памяти
    // ...
}

здесь

  • return_type – тип (класс), на который операторная функция возвращает указатель для которого выделена память по особому (нестандартным способом);
  • size – размер памяти, которая выделяется для типа return_type. Количество байт выделяемой памяти не обязательно должно совпадать со значением size, так как в теле операторной функции память можно выделять по особому.

 

4. Общая форма перегрузки оператора delete

Общая форма перегрузки оператора delete имеет следующий вид:

void operator delete(void * pointer)
{
    // освобождение памяти, на которую указывает указатель pointer
    // ...
}

здесь pointer – указатель на область памяти, которая была предварительно выделена оператором new.

 

5. Для каких типов (классов) можно применять перегруженные операторы new и delete?

Если в программе перегружаются операторы new и delete, то возникают два вида этих операторов:

  • перегруженные операторы new и delete. Эти перегруженные операторы применяются для конкретного класса (классов), в котором есть операторные функции operator new() и operator delete();
  • глобальные операторы new и delete (не перегруженные). Эти операторы применяются для классов, которые не содержат операторных функций перегружающих операторы new и delete.

Для каждого класса компилятор определяет: какой вариант операторов new и delete нужно выбрать: глобальный или для конкретного класса.

 

6. Пример перегрузки операторов new и delete внутри класса для одиночных объектов

В примере объявляется класс Complex, реализующий комплексное число. Класс содержит:

  • внутренние переменные real, imag, которые соответствуют действительной и мнимой частям комплексного числа;
  • конструктор класса;
  • методы доступа Get(), Set();
  • перегруженные операторные функции operator new(), operator delete() которые перегружают соответственно операторы new и delete. Эти функции используются строго для класса Complex.

Реализация класса Complex и функции main(), которая демонстрирует перегрузку операторов new и delete имеет следующий вид:

#include "stdafx.h"
#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;

// класс содержащий перегруженные операторы new и delete
class Complex
{
private:
    double real;
    double imag;

public:
    // конструктор
    Complex(double _real, double _imag)
    {
        real = _real;
        imag = _imag;
    }

    // методы доступа
    void Get(double * r, double * i)
    {
        *r = real;
        *i = imag;
        return;
    }

    void Set(double r, double i)
    {
        real = r;
        imag = i;
    }

    // перегруженный оператор new,
    // выделяет память заданного размера
    void * operator new(size_t size)
    {
        void * ptr;

        cout<<"Попытка выделить память\n";
        ptr = malloc(size); // попытка выделения памяти

        if (!ptr) // если память не выделена, то сгенерировать исключительную ситуацию
        {
            bad_alloc ba;
            cout<<"Ошибка выделения памяти.\n";
            throw ba;
        }
        else
        {            
            cout<<"Память выделена успешно!\n";
            return ptr;
        }
    }

    // освобождает память, выделенную перегруженной
    // операторной функцией operator new()
    void operator delete(void * ptr)
    {
        cout<<"Освобождение памяти, выделенной оператором delete.\n";
        free(ptr);
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    // демонстрация использования операторов new и delete в классе Complex
    Complex * p1; // объявить указатель на Complex

    try
    {
        // вызов операторной функции operator new()
        p1 = new Complex(5,6); // выделить память
    }
    catch (bad_alloc error)
    {
        return 1; // выйти из программы с кодом возврата 1
    }

    // освободить память
    // вызов операторной функции operator delete()
    delete p1;
    return 0;
}

В результате выполнения вышеприведенного кода, на экран будет выведен следующий результат:

Попытка выделить память
Память выделена успешно!
Освобождение памяти, выделенной оператором delete.

 

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

Операторы new и delete могут быть перегружены глобально. В этом случае использование глобальных перегруженных операторов new и delete может применяться для любых типов.
Ниже приведен пример глобальной перегрузки операторов new и delete

#include "stdafx.h"
#include <iostream>
#include <cstdlib>
using namespace std;

// класс, который содержит перегруженные операторы new и delete
class Complex
{
private:
    double real;
    double imag;

public:
    // конструктор
    Complex(double _real, double _imag)
    {
        real = _real;
        imag = _imag;
    }

    // методы доступа
    void Get(double * r, double * i)
    {
        *r = real;
        *i = imag;
        return;
    }

    void Set(double r, double i)
    {
        real = r;
        imag = i;
    }
};

// глобальный оператор new
void * operator new(size_t size)
{
    void * ptr;
    cout<<"Глобальный оператор new"<<::endl;
    cout<<"1. Попытка выделить память функцией malloc()\n";
    ptr = malloc(size); // попытка выделения памяти

    if (!ptr) // если память не выделена, то сгенерировать исключительную ситуацию
    {
        bad_alloc ba;
        cout<<"2. Ошибка выделения памяти.\n";
        throw ba;
    }
    else
    {
        cout<<"3. Память выделена успешно глобальным оператором new\n";
        return ptr;
    }
}

// глобальный оператор delete
void operator delete(void * ptr)
{
    cout<<"4. Глобальный оператор delete.\n";
    free(ptr);
}

int _tmain(int argc, _TCHAR* argv[])
{
    // демонстрация использования глобальных операторов new и delete
    Complex * p; // объявить указатель на Complex
    int * p; // указатель на int

    try
    {
        // вызов операторной функции operator new()
        p = new Complex(5,6); // выделить память
    }
    catch (bad_alloc error)
    {
        cout<<"Ошибка при выделении памяти для объекта p"<<::endl;
        return 1; // выйти из программы с кодом 1
    }

    // освободить память
    // вызов глобального оператора delete()
    delete pC;

    try
    {
        // вызов перегруженной версии оператора new
        cout<<"Попытка выделения памяти для объекта pI"<<::endl;
        pI = new int;
    }
    catch (bad_alloc error)
    {
        cout<<"Ошибка при выделении памяти для объекта pI"<<::endl;
        return 2; // выйти из программы с кодом возврата 2
    }
    delete pI;
    return 0;
}

В результате выполнения вышеприведенного кода на экран будет выведен следующий результат:

Глобальный оператор new
1. Попытка выделить память функцией malloc()
3. Память выделена успешно глобальным оператором new
4. Глобальный оператор delete.
Попытка выделения памяти для объекта pI.
1. Попытка выделить память функцией malloc()
3. Память выделена успешно глобальным оператором new
4. Глобальный оператор delete.

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

 

8. Пример перегрузки операторов new и delete в границах класса в случае выделения памяти для массива

Приведен пример перегрузки операторов new и delete для массива объектов типа Point. Класс Point определяет точку на координатной плоскости и содержит следующие объявления:

  • переменные x, y которые есть координатами точки;
  • конструкторы;
  • методы доступа Get(), Set();
  • операторные функции operator new() и operator delete(), реализующие выделение/освобождение памяти для одиночных объектов типа Point;
  • операторные функции operator new[]() и operator delete[](), которые реализуют выделение/освобождение памяти для массивов объектов типа Point.

Программный код, который реализует класс Point, имеет следующий вид:

#include "stdafx.h"
#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;

class Point
{
private:
    double x, y;

public:
    // конструкторы
    Point()
    {
        x = y = 0.0;
    }

    Point(double _x, double _y)
    {
        x = _x;
        y = _y;
    }

    // методы доступа
    void Get(double * _x, double * _y)
    {
        *_x = x;
        *_y = y;
    }

    void Set(double _x, double _y)
    {
        x = _x;
        y = _y;
    }

    // перегруженный оператор new для одиночного объекта класса Point
    void * operator new(size_t size)
    {
        void * ptr;
        ptr = malloc(size);
        if (!ptr)
        {
            bad_alloc error;
            throw error;
        }
        return ptr;
    }

    // перегруженный оператор delete для одиночного объекта класса Point
    void operator delete(void * ptr)
    {
        cout<<"Освобождение памяти оператором delete"<<::endl;
        free(ptr);
    }

    // перегруженный оператор new для массива объектов типа Point
    void * operator new[](size_t size)
    {
        void * ptr;
        cout<<"Применение перегруженного оператора new[]."<<::endl;
        ptr = malloc(size);
        if (!ptr)
        {
            bad_alloc error;
            throw error;
        }
        return ptr;
    }

    // перегруженный оператор delete для массива объектов типа Point
    void operator delete[](void * ptr)
    {
        cout <<"Удаление массива из памяти оператором delete[]"<<::endl;
        free(ptr);
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    // перегрузка операторов new и delete для массивов объектов
    Point * p1;

    try
    {
        // вызов перегруженной операторной функции operator new[]()
        // класса Point
        p1 = new Point[5]();
    }
    catch (bad_alloc error)
    {
        cout<<"Ошибка при выделении памяти для объекта p1."<<::endl;
        return 1;
    }
    delete[] p1;
    return 0;
}

В результате выполнения вышеприведенного кода на экран будет выведен следующий результат

Применение перегруженного оператора new[].
Удаление массива из памяти оператором delete[]

Важно: чтобы перегрузить операторы new и delete для массивов не обязательно перегружать эти операторы для одиночных объектов.

 

9. Пример глобальной перегрузки операторов new[] и delete[] для массивов

Ниже приведен пример глобальной перегрузки операторов new[] и delete[] для массивов класса Point

#include "stdafx.h"
#include <iostream>
#include <cstdlib>
using namespace std;

class Point
{
private:
    double x, y;

public:
    // конструкторы
    Point()
    {
        x = y = 0.0;
    }

    Point(double _x, double _y)
    {
        x = _x;
        y = _y;
    }

    // методы доступа
    void Get(double * _x, double * _y)
    {
        *_x = x;
        *_y = y;
    }

    void Set(double _x, double _y)
    {
        x = _x;
        y = _y;
    }
};

// перегруженный оператор new для одиночного объекта класса Point
void * operator new(size_t size)
{
    void * ptr;
    ptr = malloc(size);
    if (!ptr)
    {
        bad_alloc error;
        throw error;
    }
    return ptr;
}

// перегруженный оператор delete для одиночного объекта класса Point
void operator delete(void * ptr)
{
    cout<<"Освобождение памяти оператором delete"<<::endl;
    free(ptr);
}

// перегруженный оператор new для массива объектов типа Point
void * operator new[](size_t size)
{
    void * ptr;
    cout<<"Применение перегруженного оператора new[]."<<::endl;
    ptr = malloc(size);
    if (!ptr)
    {
        bad_alloc error;
        throw error;
    }
    return ptr;
}

// перегруженный оператор delete для массива объектов типа Point
void operator delete[](void * ptr)
{
    cout <<"Освобождение массива из памяти оператором delete[]"<<::endl;
    free(ptr);
}

int _tmain(int argc, _TCHAR* argv[])
{
    // глобальная перегрузка операторов new и delete для массивов объектов
    Point * p1; // указатель на класс Point
    float * p2; // указатель на float

    try
    {
        // вызов перегруженной глобальной операторной функции operator new[]()
        // класса Point
        p1 = new Point[5]();
    }
    catch (bad_alloc error)
    {
        cout<<"Ошибка при выделении памяти для объекта p1."<<::endl;
        return 1;
    }
    delete[] p1;

    // вызов глобальной операторной функции operator new[]() для базового типа float
    try
    {
        p2 = new float[10]; // вызов функции
    }
    catch (bad_alloc error)
    {
        cout<<"Ошибка при выделении памяти для объекта p2."<<::endl;
        return 2;
    }
    delete[] p2;
    return 0;
}

В результате выполнения вышеприведенного кода будет выведен следующий результат:

Применение перегруженного оператора new[].
Освобождение массива из памяти оператором delete[]

Как видно из результата, перегруженные глобальные операторные функции operator new[] и operator delete[] для массивов работают точно также как и с базовыми типами (float) так и с типами-классами (Point).

 


Связанные темы