Перевантаження операторів 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 * pC; // оголосити покажчик на Complex
int * pI; // покажчик на int

try
{
// виклик операторної функції operator new()
pC = new Complex(5,6); // виділити пам'ять
}
catch (bad_alloc error)
{
cout<<"Помилка при виділенні пам'яті для об'єкту pC"<<::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).

 


Зв’язані теми