Функция может сигнализировать о возникшей ошибке с помощью возвращаемого значения. Однако, у этого способа есть ряд недостатков:
Поэтому в синтаксис языка C++ введены исключения. Можно сообщить о возникновении исключительной ситуации с помощью оператора throw <значение>. При этом значение, передаваемое оператору throw, может содержать дополнительную информацию о возникшей ошибке и иметь примитивный или объектный тип. После вызова оператора throw выполнение функции прерывается и выполняется раскрутка стека до ближайшего блока try-catch, в котором обрабатывается исключение переданного типа. Код, в котором возможно возникновение исключения, заключается в блок try, после которого может следовать несколько операторов catch, принимающих исключения определённого типа. Если блок catch должен принимать все исключения, используется оператор catch(...).
// функция, в которой возможно возникновение исключения int foo() { //... throw 1; //... } //... void bar() { try { // при нормальном ходе выполнения возвращаемое значение сохраняется в локальную переменную int i = foo(); // дальнейшее выполнение функции } catch (int e) { // код обработки исключения } }
Переменная объектного типа в динамической памяти создаётся в два этапа:
Удаляется такая переменная тоже в два этапа:
Операторы new и delete можно перегрузить. Для этого есть несколько причин:
Операторы new и delete имеют следующие сигнатуры:
void *operator new(size_t size); void operator delete(void *p);
Оператор new принимает размер памяти, которую необходимо выделить, и возвращает указатель на выделенную память.
Оператор delete принимает указатель на память, которую нужно освободить.
class A { public: void *operator new(size_t size); void operator delete(void *p); }; void *A::operator new(size_t size) { printf("Allocated %d bytes\n", size); return malloc(size); } void A::operator delete(void *p) { free(p); }
Вместо функций malloc и free можно использовать глобальные операторы ::new и ::delete.
Рекомендуется не производить в операторе new (особенно в глобальном) какие-либо операции с объектами, которые могут вызвать оператор new. Например, для вывода текста используется функция printf, а не объект std::cout.
Операторы new и delete, объявленные внутри класса, функционируют подобно статическим функциям и вызываются для данного класса и его наследников, для которых эти операторы не переопределены.
В некоторых случаях может потребоваться перегрузить глобальные операторы new и delete. Они находятся не в пространстве имён std, а в глобальном пространстве имён.
Глобальные операторы new и delete вызываются для примитивных типов и для классов, в которых они не переопределены. Они имеют такие же сигнатуры, что и рассмотренные выше операторы new и delete.
// Для примитивных типов вызываются глобальные ::new и ::delete int *i = new int; delete i; // Для класса A вызываются переопределённые A::new и A::delete A *a = new A; delete a; // Для класса C операторы new и delete не переопределены, // поэтому вызываются глобальные ::new и ::delete C *c = new C; delete c;
Стандартный оператор new бросает исключение std::bad_alloc в случае, если не удаётся выделить достаточно памяти.
Листинг 4. Пример реализации оператора new, бросающего исключение при нехватке памяти.
void *operator new(size_t size) { void *p = malloc(size); if (p == NULL) { throw std::bad_alloc(); } return p; }
Если конструктор класса бросает любое исключение, память, выделенная под объект автоматически освобождается.
При создании массива вызывается другая форма операторов new и delete. Они имеют следующие сигнатуры:
void *operator new[](size_t size); void operator delete[](void *p);
Параметры этих функций аналогичны параметрам обычных операторов new и delete.
Массив объектов создаётся в два этапа:
Листинг 5. Пример использования операторов new[] и delete[].
// У класса A должен быть конструктор по умолчанию A *a = new A[4]; // ... delete [] a;
На этапе компиляции неизвестно, на сколько элементов выделяется массив, а значит без использования дополнительной информации невозможно вызвать деструкторы всех элементов массива.
К примеру, механизм создания и удаления массивов объектов может быть реализован следующим образом (рис. 1). С помощью функции operator new выделяется размер памяти, равный суммарному размеру всех элементов массива и размеру служебной информации. Оператор new выделяет память и возвращает, допустим, указатель p. По этому указателю записывается служебная информация, а в программу возвращается указатель на первый элемент массива. Аналогично, при вызове delete, в функцию operator delete передаётся не указатель на первый элемент массива, а указатель, на начало блока, выделенного оператором new.
Можно вызвать new на уже выделенной памяти. Такая форма оператора new называется placement new. Она может понадобиться в операционных системах реального времени и встраиваемых системах, чтобы жёстко закрепить адрес объекта.
Листинг 6. Пример создания объекта с помощью placement new.
// p - указатель на некий статический буфер // Явный вызов конструктора A *a = new(p) A; // Явный вызов деструктора a->A::~A();
Реализация placement new выглядит следующим образом:
void *operator new(size_t size, void *p) { return p; }
Если требуется, чтобы оператор new не бросал исключение в случае нехватки памяти, а возвращал ноль, можно использовать оператор new с параметром std::nothrow. Этот параметр имеет тип std::nothrow_t.
// Объявление void *operator new(size_t size, const std::nothrow_t &nt); // Пример использования A *a = new(std::nothrow) A;
Синтаксис языка C++ позволяет создавать собственные формы оператора new.
// Объявление void *operator new(size_t size, std::string &str); // Пример использования A *a = new("Object a") A;