Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование на C / C++ / C++ for real programmers.pdf
Скачиваний:
262
Добавлен:
02.05.2014
Размер:
2.04 Mб
Скачать

Грани и другие

7

 

мудрые указатели

 

Если переложить эту главу на музыку, она бы называлась «Вариации на тему умных указателей». В двух предыдущих главах я представил базовые концепции умного указателя — класса, заменяющего встроенные *-указатели, — и ведущего указателя, для которого существует однозначное соответствие с указываемым объектом. В этой главе мы продолжим эту тему и добавим в мелодию еще несколько гармоничных нот.

Интерфейсные указатели

Наверное, вы считали, что интерфейс класса полностью определяется объявлением класса, но в действительности любой класс может иметь несколько разных интерфейсов в зависимости от клиента.

Класс и его друзья видят один интерфейс, включающий всех членов класса и всех защищенных и открытых членов его базовых классов.

Производные классы видят только защищенных и открытых членов класса и его базовых классов.

Все остальные клиенты видят только открытых членов класса и его базовых классов.

Если указатель на объект преобразуется к указателю на его базовый класс, интерфейс ограничивается только открытыми членами базового класса.

Открытые, закрытые и защищенные члены; открытое и закрытое наследование; полиморфизм и дружба

— все это лишь грубые синтаксические приближения более общей концепции дизайна: один объект может иметь много специализированных интерфейсов.

Дублирование интерфейса

Давайте посмотрим, можно ли обобщить эту концепцию с помощью еще более умных (назовем их «мудрыми») указателей (smarter pointers). Для начала нам придется на некоторое время покинуть своего старого друга, оператор ->. Одно из ограничений оператора -> заключается в следующем: чтобы использовать указатель, клиент также должен знать все об интерфейсе указываемого объекта.

class Foo {

// Интерфейсная часть, которую бы вам хотелось спрятать подальше

};

Ptr<Foo> pf(new Foo);

Хммм. Чтобы клиент мог пользоваться указателем, нам придется рассказать ему все что только можно об указываемом объекте Foo. Не хотелось бы. Ниже показан альтернативный вариант. Терпение — все не так страшно, как кажется на первый взгляд.

class Foo {

friend class Pfoo; protected:

Foo();

public:

94

void DoSomething(); void DoSomethingElse();

};

class PFoo { private:

Foo* foo; public:

PFoo() : foo(new Foo) {}

PFoo(const PFoo& pf) : foo(new Foo(*(pf.foo))) {} ~PFoo() { delete Foo; }

PFoo& operator=(const PFoo& pf)

{

if (this != &pf) { delete foo;

foo = new Foo(*(pf.foo));

}

return *this;

}

void DoSomething() { foo->DoSomething(); }

void DoSomethingElse() { foo->DoSomethingElse(); }

};

Произошло следующее: мы воспользовались удобными средствами копирования/вставки текста вашей среды программирования и продублировали в указателе интерфейс указываемого объекта. Чтобы не лениться и не взваливать всю тяжелую работу по делегированию на оператор ->, мы решительно реализовали все функции класса так, что каждая из них перенаправляет вызов функции-прототипу указываемого объекта. Указатели, воспроизводящие интерфейс указываемого объекта, называются

интерфейсными указателями (interface pointers).

Маскировка указываемого объекта

Поначалу кажется, что реально мы ничего не добились. Чтобы подставляемые функции работали, интерфейс класса Foo все равно должен находиться в файле .h перед объявлением класса PFoo. Тем не менее, смирившись с небольшими дополнительными вычислениями для наших указателей, мы получаем быстрый и ощутимый результат.

class

Foo1;

// Все, что клиент видит и знает о Foo

class

PFoo1

{

private:

Foo1* foo; public:

PFoo1();

PFoo1(const PFoo1& pf); ~PFoo();

PFoo1& operator=(const PFoo1& pf);

void DoSomething(); void DoSomethingElse();

};

class Foo1 {

friend class PFoo1; protected:

Foo1();

95

public:

void DoSomething(); void DoSomethingElse();

};

PFoo1::PFoo1() : foo(new Foo1) {}

PFoo1::PFoo(const PFoo1& pf) : foo(new Foo1(*(pf.foo))) {}

PFoo1::~PFoo()

{

delete foo;

}

PFoo1& PFoo1::operator=(const PFoo1& pf)

{

if (this != &pf) { delete foo;

foo = new Foo1(*(pf.foo));

}

return *this;

}

void PFoo1::DoSomething()

{

foo->DoSomething();

}

void PFoo1::DoSomethingElse()

{

foo->DoSomethingElse();

}

Foo1::Foo1()

{

}

void Foo1::DoSomething()

{

cout << “Foo::DoSomething()” << endl;

}

void Foo1::DoSomethingElse()

{

cout << “Foo::DoSomethingElse()” << endl;

}