среда, 2 января 2008 г.

О ссылающихся друг на друга объектах

Очень часто возникает ситуация, при которой объект, объявленный раньше, должен вызывать метод объекта, объявленного позже. Но поменять объявления местами не представляется возможным, ибо "нижестоящий" по коду ссылается на "вышестоящего".

Пример:

class foo
{
public:
void test( bar * b ) { b->print( "foo" ); }
};

class bar
{
public:
void print( const char * txt ) { printf_s( "%s\n", txt ); }
private:
foo m_obj;
};
_Winnie C++ Colorizer

При попытки скомпилировать это, компилятор пошлёт к такой-то матери. Ну и понятно, ведь на момент компиляции foo, нет никакой информации о bar. Это же не позволяет использовать обычные указатели на методы класса.

Можно попытаться выкрутиться, используя интерфейсы:

struct i_bar
{
virtual void print( const char * txt ) = 0;
};

class foo
{
public:
void test( i_bar * b ) { b->print( "foo" ); }
};

class bar : public i_bar
{
public:
void print( const char * txt ) { printf_s( "%s\n", txt ); }
private:
foo m_obj;
};
_Winnie C++ Colorizer

Данный код скомпилиться и будет работать. Но этот подход требует создание "костылей", что не радует глаз. К тому же нарушается иерархия. foo приходиться знать о bar, хотя знать о нём он не обязан. Всё что нужно - это вызов некой "абстрактной" функции/метода. А чья она - это уже не важно.

Можно "разнести" объявление и реализацию классов:
// bar.h
#include "foo.h"

class bar
{
public:
void print( const char * txt ) { printf_s( "%s\n", txt ); }
private:
foo m_obj;
};

// foo.h
class bar;
class foo
{
public:
void test( bar * b );
};

// foo.cpp
#include "foo.h"
#include "bar.h"

void foo::test( bar * b )
{
b->print( "foo" );
}
_Winnie C++ Colorizer

Но раскидывать код по файлам не всегда бывает удобно. К тому же снова идёт нарушении иерархии, как в предидущем пункте. Но как бы то ни было, этот метод очень распространён. И я пользовался им до недавнего времени.

Пока не начала курить boost =)
typedef boost::function< void( const char * ) > boost_func;

class foo
{
public:
void test( boost_func & func ) { func( "foo" ); }
};

class bar
{
public:
void print( const char * txt ) { printf_s( "%s\n", txt ); }
private:
foo m_obj;
};


int _tmain( int, char * )
{
foo f;
bar b;

f.test( boost_func( boost::bind( &bar::print, &b, _1 ) ) );

return 0;
}
_Winnie C++ Colorizer

Вуаля! boost::bind + boost::function решает все проблемы. Раскидывать ничего не надо. К томуже foo не приходиться ничего знает о bar. Одни плюсы =)

Вот только я ещё не до конца понял, как это штука работает. Но работает - факт.

10 комментариев:

Sergey Miryanov комментирует...

1.
template <class T>
class foo
{
public:
void test (T * t) { t->print("zx"); }
};

2. typedef void (*TFunc)(char * zxc);
class foo { public: void test(TFunc func) { func("fsdfsd"); } };

ну вот как то так.

Timai комментирует...

"template <class T>
class foo
{
public:
void test (T * t) { t->print("zx"); }
};"

А если у нас есть 2 разных класса, методы которых должен вызывать foo?
Создавать для каждого свой объект?
т.е.

class bar_1 {...};
class bar_2 {...};

foo< bar_1 > foo_1;
foo< bar_2 > foo_2;

А если foo нужен только один? Нестыковочко.

----------------------

Тут у тебя просто указатель на функцию, а не на метод. Тогда уж должно быть:
typedef void (bar::*TFunc)( char * zxc );
Но как я уже писал выше - не скомпилицо.

Sergey Miryanov комментирует...

Ага, про это все я подумал когда написал коммент.

Примеры надо корректнее делать =)

CyberZX комментирует...

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

А boost.function очень мощный инструмент, но применение его здесь ведёт к обобщению типа входных параметров, что не всегда приемлимо.
к тому же если вызывается больше одной функции объекта bar*, то надо на каждую функцию отдельно передавать по отдельному объекту boost.function.

ну ещё не стоит забывать, что boost.function занимает на стеке 32 байта + динамически выделяет память, если нужно больше. а указатель на объект 4-8 байт.

Timai комментирует...

forward declaration и отделение описания класса от объявления

Ну про это я во 2-ом варианте описывал. Но иногда бывает что весь класс с реализацией может занять 50 строк. Заводить для этого отдельный .cpp? Но раньше так и делал, да.

---

ну ещё не стоит забывать, что boost.function занимает на стеке 32 байта + динамически выделяет память, если нужно больше. а указатель на объект 4-8 байт.
Ну про скорость я умышленно ничего не написал =) К тому же такие "сложные" вызовы использую только при загрузке. Так что не критично.

з.ы. Как грустно без развлетвленных комментариев =(

CyberZX комментирует...

можно в одном файле совместить.

// file - foo.h

class foo
{
void test(bar* bar);
};

#include "bar.h"
inline void foo::test(bar* bar)
{
...
}

немного правда придётся чуток заморочиться с гвардами включения, но это просто.

зы: говорят, что wordpress всё таки поддерживает древовидные комментарии. надо бы посмотреть как их включать

nobu комментирует...

Чмоки всем в чате! ^_^

У меня была как-то такая трабла, когда надо было написать IDL для весьма накрученного и сложносочиненного куска проекта.

Там были ссылки всех видов, вверх, вниз, перекрестом, даром что не по графу.

В итоге средствами IDL это реализовать не получилось, из-за случаев, когда 1 ссылается на 2, а 2 на 1 :) + ко всему, он еще и не умел разделять функции с одним именем и разными конструкторами.

Sergey Miryanov комментирует...

cyberzx
к тому же если вызывается больше одной функции объекта bar*, то надо на каждую функцию отдельно передавать по отдельному объекту boost.function

если надо вызывать у объекта bar* больше одной функции, то вызывающий либо знает о bar и тогда стоит хранить указатель на bar и явно вызывать нужные функции, либо действительно передавать несколько объектов boost::function, что полностью позволяет отвязаться от знания о "внешнем мире".

timai
я тут думал над примером с фабрикой. да, c boost::function получается гибче, без честных виртуальных функций. очень круто получается.

Анонимный комментирует...

Только обрадовался в ЖЖ, как ты убил и разочаровал...
Подобное поведение легко сделать и на указателях на метод, boost.function - обыкновенный функциональный объект, касательно его реализации - Александреску, пятая глава.
Boost.Bind - банальное замыкание.
Издержки типа вызова по указателю все равно не избежать.

Потому методы типа интерфеёсов, разнесения реализации и декларации, функциональных объектов Boost.Function либо Loki::Function, сигналов слотов по типу Qtшных либо Boost.Signals - одного поля ягоды, увы.

Прямое решение проблемы - возможность писать такой код не создавая дополнительный уровень косвенности (а как следствие издержки от него) - без модификации языка невозможно.

А об убогости хидеров я уже говорил http://zabivator.livejournal.com/204657.html

Timai комментирует...

zabivator
Подобное поведение легко сделать и на указателях на метод
Нельзя написать void (foo:*pfunc)() если компилятор ещё не знает о foo. Или под "указателем на метод" подразумелось что-то иное?


Потому методы типа интерфеёсов, разнесения реализации и декларации, функциональных объектов Boost.Function либо Loki::Function, сигналов слотов по типу Qtшных либо Boost.Signals - одного поля ягоды, увы.


Ну по большому счёту это просто буквы. Просто они складыватьсыя могут по разному, ага. Ну да, все эти вещи для одной цели. Вопрос в удобстве использования