Интерфейсные указатели
Среда, 08 Сен 2010 12:49Наверное, вы считали, что интерфейс класса полностью определяется объявлением класса, но в действительности любой класс может иметь несколько разных интерфейсов в зависимости от клиента.
Класс и его друзья видят один интерфейс, включающий всех членов класса и всех защищенных
и открытых членов его базовых классов.
Производные классы видят только защищенных и открытых членов класса и его базовых
классов.
Все остальные клиенты видят только открытых членов класса и его базовых классов.
Если указатель на объект преобразуется к указателю на его базовый класс, интерфейс
ограничивается только открытыми членами базового класса.
Открытые, закрытые и защищенные члены; открытое и закрытое наследование; полиморфизм и дружба — все это лишь грубые синтаксические приближения более общей концепции дизайна: один объект может иметь много специализированных интерфейсов.
Дублирование интерфейса
Давайте посмотрим, можно ли обобщить эту концепцию с помощью еще более умных (назовем их «мудрыми») указателей (smarter pointers). Для начала нам придется на некоторое время покинуть своего старого друга, оператор ->. Одно из ограничений оператора -> заключается в следующем: чтобы использовать указатель, клиент также должен знать все об интерфейсе указываемого объекта.
class foo {
// интерфейсная часть, которую бы вам хотелось спрятать подальше
};
Ptr<Foo> pf(new foo);
Хммм. Чтобы клиент мог пользоваться указателем, нам придется рассказать ему все что только можно об указываемом объекте foo. Не хотелось бы. Ниже показан альтернативный вариант. Терпение — все не так страшно, как кажется на первый взгляд.
class foo { friend class Pfoo; protected: foo О; public:
void DoSomethingO ; void DoSomethingElseQ ;
};class pfoo {
private:
foo* foo; public:
PFooQ : 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 DoSomethingO { foo->DoSomething() ; } void DoSomethingElseQ { foo->DoSomethingElse() ; }
Произошло следующее: мы воспользовались удобными средствами копирования/вставки текста вашей среды программирования и продублировали в указателе интерфейс указываемого объекта. Чтобы не лениться и не взваливать всю тяжелую работу по делегированию на оператор ->, мы решительно реализовали все функции класса так, что каждая из них перенаправляет вызов функции-прототипу указываемого объекта. Указатели, воспроизводящие интерфейс указываемого объекта, называются интерфейсными указателями (interface pointers).
Маскировка указываемого объекта
Поначалу кажется, что реально мы ничего не добились. Чтобы подставляемые функции работали, интерфейс класса foo все равно должен находиться в файле .h перед объявлением класса pfoo. Тем не менее, смирившись с небольшими дополнительными вычислениями для наших указателей, мы получаем быстрый и ощутимый результат.
class Fool; // все, что клиент видит и знает о foo
class pfoo! {
private:
Fool* foo; public:
PFoolQ ;
PFool(const pfooi& pf) ;
~pfoo() ;
pfoo!& operator=(const pfoo!& pf) ;
void DoSomethingO; void DoSomethingElseQ ;
};
class Fool { friend class PFool; protected: FoolQ ;
public:
void DoSomethingO ; void DoSomethingElseQ ;
PFool: :pfoo!() : foo(new Fool) {}
PFool:
Foo(const pfoo!& pf) : foo(new Fool(*(pf .foo))) {}
PFool: :~pfoo()
{
delete foo; }
pfoo!& PFool:
perator=(const pfoo!& pf) {
if (this != &pf) { delete foo;
foo = new Fool(*(pf .foo)) ; }
return *this; }
void PFool: : DoSomethingO
{
foo->DoSomething() ;
}
void PFool:
oSomethingElse() {
foo->DoSomethingElse() ;
}
Fool: :foo!()
{ }
voi d Fool : : DoSomethi ng ()
{
cout « “foo: : DoSomethingO” « endl ;
}
void Fool: : DoSomethi ngEl se() {
cout « “foo: : DoSomethi ngEl se()” « endl;
Видите, что здесь происходит? Для клиента класс foo перестает существовать. Для всех практических целей указатель стал самим объектом. С таким же успехом мы могли все переименовать, убрать Р перед указателем и заменить имя foo чем-нибудь более закрытым и загадочным. Единственное, что говорит о существовании второго класса, — предварительное объявление cl ass foo ; .
Цена всего происходящего — вызов не подставляемых (noninline) функций в каждой функции класса указателя. Для некоторых немногочисленных приложений и классов даже эта малая цена может стать неприемлемой. В таких случаях существуют две альтернативы для повышения скорости: использование умных указателей на базе оператора -> и использование интерфейсных указателей с занесением объявления класса указываемого объекта в файл .h и отказом от всех преимуществ инкапсуляции. Как вы убедитесь в оставшейся части этой главы, второй вариант все же имеет некоторые достоинства.
Изменение интерфейса
Одно из преимуществ интерфейсных указателей — в том, что они фактически позволяют изменить интерфейс указываемого объекта без внесения изменений в его класс. Интерфейс, представленный интерфейсным указателем, находится полностью в вашем распоряжении; вы можете исключить из него некоторые функции указываемого объекта, изменить сигнатуры, добавить ваши собственные дополнительные функции и просто хорошо провести время, заново изобретая указываемый объект.