Если мы уж взялись заниматься умными указателями, то очень быстро придем к выводу, что неплохо ограничить их свободу так, чтобы два указателя не указывали на один объект. Далее я их называю ведущими указателями. Для этого нужно реализовать буквально три-четыре правила:
Если же мы хотим получить однозначное соответствие объекта и его ведущего указателя, то нужно запретить создание объекта, кроме как при помощи ведущего указателя, и запретить создание ведущего указателя, кроме как специальной функцией. Последнее в общем не обязательно, а первое весьма важно.
Такие простые, но замечательно полезные механизмы просто сами набираются на клавиатуре сначала в виде класса, а потом в виде шаблона класса (мы же не последний день на свете живем, пригодится еще).
class CThat
{
private:
int i;
public:
CThat (int _i=0):i(_i){};
CThat (const CThat& _that):i(_that.i){};
CThat& operator=(const CThat& _that)
{
if (this == &_that)
return *this;
i = _that.i;
return *this;
};
};
class MasterPointer
{
private:
CThat* t;
public:
// MasterPointer():t(new CThat){};
MasterPointer(CThat _that=0):t(new CThat(_that)){};
MasterPointer(const MasterPointer& mp):t(new CThat((*mp.t))){};
~MasterPointer(){delete t;};
MasterPointer& operator=(const MasterPointer& mp)
{
if (this!=&mp)
{
delete t;
t= new CThat(*(mp.t));
}
return *this;
};
};
Напоминать не надо, что this - это указатель на самого себя? Кстати и оказалось, что для реализации ведущего указателя класс указываемого объекта должен и сам иметь:
Кстати, пока я нахожусь в этой теме, хочу заметить, понятия "конструктор по умолчанию", "конструктор без аргументов" и "конструктор, генерируемый компилятором" легко спутать, если сразу не уяснить их отношения. Конструктор по умолчанию - это конструктор, который может быть вызван без указания агрументов. Он может иметь аргументы, но все они имеют значения по умолчанию. Конструктор без аргументов - тот, у которого аргументы вообще не определены. Конструкторы, генерируемые компилятором - два конструктора - без аргументов и копии - создаются только в том случае, если Вы не определили других конструкторов.
Вернемся же однако к коду. Я определил два конструктора без аргументов и один закомментировал. Оставленный конструктор меня больше устраивает, но он не годится для преобразования кода в шаблон. Поэтому в шаблоне используется первый. Оператор присваивания проверяет, не происходит ли присвоение самому себе. Это всегда важно, поскольку если не проверить, то следующим шагом мы уничтожим содержимое, так и не оставив себе ничего на память. Поскольку все вроде нормально, рисуем шаблон.
template <class T> class MP
{
private:
T* t;
public:
MP():t (new T){};
MP(const MP<T>& mp):t(new T((*mp.t))){};
~MP(){delete t;};
MP<T>& operator=(const MP<T>& mp)
{
if (this!=&mp)
{
delete t;
t= new T(*(mp.t));
}
return *this;
};
};
Чувствуете ли Вы, что идеология умных указателей близка к идеологии COM? Если еще нет, то готовьтесь - сходство явится самое ближайшее время. IUnknown, QueryInterface, ClassFactory и интерфейсы объектов - все полностью взято из идиоматики умных указателей.