Шаг 2 - Умные указатели

Сначала договоримся о терминах. Перегрузка функций (overloading) - многократное определение функции с разным набором аргументов (сигнатурой). В сигнатуру включается модификатор const, но не включается возвращаемое значение. Перегрузка операторов(overloading) - то же самое (операторы это собственно функции, только имя у них предопределенное), ПЛЮС само определение такого оператора. Если вы определили оператор для какого-то класса, это уже считается перегрузкой. Переопределение функций (overriding) - определение функций в субклассах с тем же именем. Правила переопределения функций достаточно сложны, и я не хотел бы грузить вас ими. Скажу только, что в Object Pascal они не в пример яснее, и компилятор укажет вам, что вы тормоз, если тормознете. А в Плюсах компилятор просто скроет функции, которые вы переопределите неправильно. Ну мы ему отомстим.

В C++ мы можем перегрузить почти все операторы, за исключением нескольких. Во всяком случае, оператор -> перегружается, и это имеет значение крайне важное. Кстати, он называется селектором (member selector). Итак, попробуем:

#include <mem.h>
class Cthat
{
public:
	void doIt(void){return;};
};

class CPthat
{
private:
	Cthat* aThat;
public:
	CPthat(Cthat* _that=NULL):aThat(_that){};
	~CPthat(){ if (aThat) delete aThat;};
	operator Cthat* (){return aThat;}; // Оператор преобразования типа
	CThat* operator->() {return aThat;}; // Оператор селектора ->
	CPthat operator+(ptrdiff_t _offset) {return CPthat(aThat+_offset);};
//	                 ^^^^^^^^^

};

int main ()
{
	Cthat* aThat = new Cthat;
	aThat->doSomething();
	CPthat pthat(new Cthat);
	pthat->doIt(); // Вариант обращения через ->
	((Cthat*)pthat)->doIt (); //Вариант обращения через Cthat*
	delete aThat;
	return 0;
}

Что получилось: Имеем класс Cthat, который может иметь экземпляры, хотя и не имеет наполнения, и может исполнить пустую функцию. (Обратите внимание. Пустой объект имеет размер 1, и если добавить переменную char, то размер будет тот же. Экземпляры пустых объектов существуют, и они различаются.) Имеем класс объекта-указателя CPthat, в котором храним обычный указатель, но доступ к нему ограничиваем, и перегружаем для него операторы:

  1. приведения типа Cthat
  2. member selector ->.
  3. Операторы арифметики указателей. Я указал только один, сложение.

Идея ясная. Нужно переопределить все восемь, или не переопределять их вовсе. Вопрос в том, направлен ли Ваш указатель на массив, или нет. Во всяком случае, не спешите с этим. Да, и в Ваших плюсах скорее всего тип ptrdiff_t надо заменить на ptr_diff. Я просто дома на BC3.1 все проверяю.

Что здесь хорошего? Мы получили класс объектов-указателей, которые можно смело применять вместо настоящих. Деструктор ~CPthat() уничтожает указуемый объект, поскольку сам по себе последний не имеет имени, и без своего указателя утрачивает идентичность. Проще говоря, останется в нашей памяти навечно, как герой. Ну можно конечно вызывать деструктор и явно, а что? Вот так:

pthat->~Cthat();

Тогда удаление уберите из деструктора указателя.

Напоследок сделаем очевидный шаг - сделаем умный указатель параметризированным классом.

template <class T>
class SmartPointer
{
private:
	T* tObj;
public:
	SmartPointer(T* _t=NULL):tObj(_t);
	~SmartPointer(){if (tObj) delete tObj; };
	operator T*(){return tObj;};
	T* operator->(){return tObj;};
};

Для интереса посмотрите, как сделан auto_ptr в STL.

Передохнем. Кофе. Джоггинг. Пиво. Сигарета. Нужное подчеркнуть, выпить, покурить.


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Albert Makhmutov - 21.03.2001