Шаг 27 - Умные указатели. Перегрузка operator*, operator(),operator->*

Пробегая по верхам интересных идиом я упустил одну важную вещь. Поначалу она была не так важна, но пришло время замучать и ее. Я имею в виду то, что наши замечательно умные указатели, smart pointers, вообще-то имеют неполную семантику. То есть, они не полностью имитируют обычные, настоящие указатели. За примерами не надо ходить далеко - попробуем разыменовать смарт или вызвать функцию по указателю:

obj  = *(smart_ptr);
(obj->*ptr_to_funct) ( some_parameter);

С первой проблемой рассчитаться легко. Если Вы НЕ читаете сейчас этот Шаг, не беспокойтесь - решение придет само, в тот момент, когда задача возникнет.

//Ясно, это реализации перегруженных операторов-селекторов.
CSmth* operator->() const 
	{ return  prt_real;};
CSmth& operator* () const 
	{ return *ptr_real;};
operator CSmth*  () const 
	{ return  ptr_real;};

А что вторая проблема? Да, тут ситуация намного серьезнее, и если Вы опять-таки не читаете этот Шаг, то нужно немедленно прочесть его - или первоисточник - статью Мейерса в Dr. Dobb's Journal. Только там придется продираться через тучные стада шаблонов и долгих рассуждений. Без шаблона конечно не обойтись, но нужно ухватить хотя бы идею. Поэтому сделаем так, как нормальный человек читает детективы Марининой: первые и последние две страницы.

Сначала, кто такой operator->*. Это который вызывает функцию-член по указателю. Такую функцию нужно вызывать с указанием объекта, если из другой функции-члена, то в виде (this->*mpf)() или (*this).*mpf().

// Этот класс используется так же дальше
class CSmth
{
public:
	int a;
	int pf (void) { return a;};
};
typedef int (CSmth::*PF)(void);

Если мы нарисуем умный указатель на объект класса CSmth, определять operator->*() нужно самостоятельно. Что он должен вернуть? Нечто такое, к чему можно применить operator(). То есть, это снова proxy-объект. Мейерс называет его "незавершенный вызов функции-члена" (Pending Member Function Calling). Он должен знать, к какому объекту применяется, и знать об указателе на функцию, то есть он должен иметь в себе указатели на них обоих, и инициализировать их в конструкторе. А operator() должен возвращать уже нужный нам int, или все что угодно другое, что может вернуть указываемая функция.

// класс незавершенного вызова. Это самое важное.
class pmfc
{
private:
	// два указателя - на объект и на функцию
	CSmth* m_smth;	
	PF m_pfunct;
public:
	// конструктор
	pmfc ( CSmth*& _smth, PF& _pfunct) 
		: m_smth(_smth), m_pfunct(_pfunct){};
	// вызов конечной функции из оператора ()
	int operator()() const 
		{ return (m_smth->*m_pfunct)();};
};
// класс умного указателя.
class CPtr
{
private:
	CSmth* a;
public:
	CPtr() { a = new CSmth(); };
	~CPtr() { delete a;};
	CSmth* operator->() const { return  a;};
	CSmth& operator* () const { return *a;};
	operator CSmth* ()  const { return  a;}; 
	// возвращает PMFC. Это тоже важно.
	pmfc operator->*(  PF _pf) 
		{ return pmfc (a, _pf );};
};
// проверим все
int main ()
{
	CPtr t;
	t-&g;ta = 10;
	// заодно проверим operator*
	(*t).a = 16;
 	int b = 0;
	// получили указатель на функцию.
	PF lpF = &CSmth::pf;
	// вызвали функцию по указателю при помощи нашей конструкции
	b = (t->*lpF)();
	return 0;
}

С тоской взглянув на полученный результат, сразу осознаешь, что без шаблонов не обойтись - ведь нужно обслуживать разные типы указателей на функции. Но зато мы минимум знаем, как решать эту проблему. Еще раз испытали proxy-объекты. Потрогали указатели на функции и функции члены. Перегрузили операторы * и (). И если встанет проблема - то знаем, где искать решение (у Скотта Мейерса).


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