Возникли вопросы про сабжу. Как это можно сделать и вообще правильно ли это? По мне так такая штука очень полезная, например есть набор модулей с общим интерфейсом, которые общаются через сообщения, но данные в сообщениях могут быть разного типа и их количество(типов данных) может со временем расти или в том же обзёрвере в его push варианте. Может быть такое возникает при ошибках проектирования или это нормальная ситуация?
Сейчас вижу несколько способов как такое реализовать:
1) void* По мне это грубый хак без какой либо проверки типов.
2) boost::any. Вообщем намного лучше чем первый вариант, есть возможность проверить тип, хотя все равно тоже самое. Но эта возможность(через type_info) не очень стандартизирована и при компиляции разных модулей разными компиляторами может не прокатить, а также тратиться дополнительное время на эту самую проверку.
3) boost::variant. Реализация паттерна визитор. Нет проблем с проверкой типов, но количество типов изначально ограничено и при добавлении нового нужно переписывать код этих самых визиторов.
4) сериализация. Универсальный способ, но зачем нужно сериализовывать объект если нужна только ссылка на него.
В языках с динамической(утиной) типизацией таких вопрос не возникает. В C# насколько я знаю(хотя плохо знаю) передают объект CObject.
Вообщем теряюсь в догадках как это можно нормально реализовать на C++ и вообще правильно ли это или нет.
Здравствуйте, AndreyM16, Вы писали:
AM>4) сериализация. Универсальный способ, но зачем нужно сериализовывать объект если нужна только ссылка на него.
5) Завести модуль, который рулит передачей параметров и сообщений. Все остальные регят в нём типы, которые используют, сообщения, и конвертилки типов, если нужны/доступны.
Дальше аналог any, но свой и не привязанный к реализации RTTI...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, AndreyM16, Вы писали:
AM>>4) сериализация. Универсальный способ, но зачем нужно сериализовывать объект если нужна только ссылка на него.
E>5) Завести модуль, который рулит передачей параметров и сообщений. Все остальные регят в нём типы, которые используют, сообщения, и конвертилки типов, если нужны/доступны.
E>Дальше аналог any, но свой и не привязанный к реализации RTTI...
Как выделенное можно реализовать? Или нужно изначально задать определенные типы которые могут быть использованы(хотя они могут быть определены, только в этом спомогательном модуле) и добавлять новые если появляются, или использовать type_info, я других способов не вижу. А вообще, потребность в этом не ошибка проектирования?
Здравствуйте, AndreyM16, Вы писали:
AM>4) сериализация. Универсальный способ, но зачем нужно сериализовывать объект если нужна только ссылка на него.
если система маленькая, то void* + GetType() достаточно
сериализация подходит для серьезной системы (event bus)
лучше отказаться от "ссылок" и передавать данные по значению. так вы решите проблемы с разными модулями (std::string через границы dll, runtime, etc) и временем жизни данных
в сериализации одна из больших подзадач — это поддержка версионности (через xml можно все обрулить)
общее название: message queue, event bus. можете в гугле поискать
Здравствуйте, AndreyM16, Вы писали:
AM>2) boost::any. Вообщем намного лучше чем первый вариант, есть возможность проверить тип, хотя все равно тоже самое. Но эта возможность(через type_info) не очень стандартизирована и при компиляции разных модулей разными компиляторами может не прокатить, а также тратиться дополнительное время на эту самую проверку.
предлагаю обсудить такой набросок (кое где можно подправить, но как иллюстрация пойдет):
enum any_message { any_error, any_clone, any_alloc, any_free };
template< typename T >
struct holder {
T value;
holder() : value() {}
static void * apply ( any_message msg, void const * src ) {
switch ( msg ) {
case any_alloc: {
return 0;
} case any_clone: {
void * ptr = new T( * static_cast<T const *>( src ) );
std::cout << "clone:" << src << " -> " << ptr << std::endl;
return ptr;
} case any_free: {
std::cout << "free:" << src << std::endl;
delete static_cast<T const *>( src );
return 0;
} default: {
assert( 0 );
}
}
}
};
struct any {
void * content;
void * (* id)( any_message, void const * );
template< typename T >
any ( T const & src ) : content( new T( src ) ), id( &holder<T>::apply ) { std::cout << "alloc:" << (void *)content << std::endl; }
any ( any const & rhs ) : content( rhs.id( any_clone, rhs.content ) ), id( rhs.id ) {}
~any () { this->id( any_free, this->content ); }
any & swap ( any & rhs )
{
std::swap( this->content, rhs.content );
std::swap( this->id, rhs.id );
return *this;
}
any & operator = ( any rhs ) { rhs.swap( *this ); return *this; }
template< typename T > T * get() {
return ( this->id == &holder<T>::apply ) ? static_cast<T *>( this->content ) : 0;
}
};
int main()
{
any x = int( 1 );
any y = int( 2 );
any z = float( 2 );
x = y;
assert( *x.get<int>() == 2 );
x = z;
assert( x.get<int>() == 0 );
return 0;
}
Здравствуйте, AndreyM16, Вы писали:
AM>Возникли вопросы про сабжу. Как это можно сделать и вообще правильно ли это? По мне так такая штука очень полезная, например есть набор модулей с общим интерфейсом, которые общаются через сообщения, но данные в сообщениях могут быть разного типа и их количество(типов данных) может со временем расти
Здравствуйте, night beast, Вы писали:
NB>Здравствуйте, AndreyM16, Вы писали:
AM>>2) boost::any. Вообщем намного лучше чем первый вариант, есть возможность проверить тип, хотя все равно тоже самое. Но эта возможность(через type_info) не очень стандартизирована и при компиляции разных модулей разными компиляторами может не прокатить, а также тратиться дополнительное время на эту самую проверку.
NB>предлагаю обсудить такой набросок (кое где можно подправить, но как иллюстрация пойдет): NB>
...
NB>
Это не будет работать если есть по крайней мере два отдельно компилирумых модуля.
Здравствуйте, AndreyM16, Вы писали:
AM>>>2) boost::any. Вообщем намного лучше чем первый вариант, есть возможность проверить тип, хотя все равно тоже самое. Но эта возможность(через type_info) не очень стандартизирована и при компиляции разных модулей разными компиляторами может не прокатить, а также тратиться дополнительное время на эту самую проверку.
NB>>предлагаю обсудить такой набросок (кое где можно подправить, но как иллюстрация пойдет):
немного подумав решил что механизм виртуальности предпочтительней.
AM>Это не будет работать если есть по крайней мере два отдельно компилирумых модуля.
объектника или длл? если объектника, то в чем именно косяк?
Здравствуйте, night beast, Вы писали:
NB>Здравствуйте, AndreyM16, Вы писали:
AM>>>>2) boost::any. Вообщем намного лучше чем первый вариант, есть возможность проверить тип, хотя все равно тоже самое. Но эта возможность(через type_info) не очень стандартизирована и при компиляции разных модулей разными компиляторами может не прокатить, а также тратиться дополнительное время на эту самую проверку.
NB>>>предлагаю обсудить такой набросок (кое где можно подправить, но как иллюстрация пойдет):
NB>немного подумав решил что механизм виртуальности предпочтительней.
AM>>Это не будет работать если есть по крайней мере два отдельно компилирумых модуля.
NB>объектника или длл? если объектника, то в чем именно косяк?
Я имел ввиду dll так как в вашем any типы индексируются ссылкой на инстанцированный класс holder, и в различных dll для одного типа они будут разными.
Здравствуйте, AndreyM16, Вы писали:
NB>>>>предлагаю обсудить такой набросок (кое где можно подправить, но как иллюстрация пойдет):
NB>>немного подумав решил что механизм виртуальности предпочтительней.
AM>>>Это не будет работать если есть по крайней мере два отдельно компилирумых модуля.
NB>>объектника или длл? если объектника, то в чем именно косяк?
AM>Я имел ввиду dll так как в вашем any типы индексируются ссылкой на инстанцированный класс holder, и в различных dll для одного типа они будут разными.
Здравствуйте, AndreyM16, Вы писали:
AM>2) boost::any. Вообщем намного лучше чем первый вариант, есть возможность проверить тип, хотя все равно тоже самое. Но эта возможность(через type_info) не очень стандартизирована и при компиляции разных модулей разными компиляторами может не прокатить, а также тратиться дополнительное время на эту самую проверку.
Тут надо понимать, что при компиляции разных модулей разными компиляторами может вообще ничего не проканать, кроме plain С. И даже обычный _HAS_ITERATOR_DEBUGGING=0 в MSVC 2010 способен поломать бинарную совместимость std::string между вашим модулем и CRT. Так что бы порекомендовал осетра маленько урезать и сперва четко определиться с набором условий, для которого вам нужно решение.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, AndreyM16, Вы писали:
AM>А вообще, потребность в этом не ошибка проектирования?
— полностью согласен. Сколько раз сталкивался с таким, всё равно этот "произвольный объект" и финальный свитч "по типам" в конце концов заменялся на иное решение, более "прямого" характера. Поэтому — не тратьте время, если нет горящего дедлайна. Обдумайте спокойно: как можно генерализовать необходимые операции? зачем понадобился этот нашлёпок с "любым типом"?
Здравствуйте, Mr.Delphist, Вы писали:
AM>>А вообще, потребность в этом не ошибка проектирования?
MD> — полностью согласен. Сколько раз сталкивался с таким, всё равно этот "произвольный объект" и финальный свитч "по типам" в конце концов заменялся на иное решение, более "прямого" характера. Поэтому — не тратьте время, если нет горящего дедлайна. Обдумайте спокойно: как можно генерализовать необходимые операции? зачем понадобился этот нашлёпок с "любым типом"?
для расширяемости. например поддержки в сервере новой операции без переделки старых клиентов, её не использующих. странно, что ты проспал использование xml, например, для этих целей
Здравствуйте, AndreyM16, Вы писали:
AM>Как выделенное можно реализовать? Или нужно изначально задать определенные типы которые могут быть использованы(хотя они могут быть определены, только в этом спомогательном модуле) и добавлять новые если появляются, или использовать type_info, я других способов не вижу.
В смысле? Какие проблемы с реализацией? Регишь имя типа в виде строки и интерфейс для доступа. Полагаешься на то, что твои плагины не будут называть одним именем два разных типа. Это ничего не ухудшает, так как просто при программировании на С++ ты на это тоже полагаешься...
Если всё и всегда inprocess, то можно обойтись и без доп. модуля. И всё регать локально. В каждом из модулей.
Смотри. Пишешь что-то типа
class MyAny {
public:
MyAny( const MyAny& ); // тривиально, через data->Clone()
~MyAny(); // тоже тривиально.template<typename T>
static MyAny Create( T& toCreate )
{
return MyAny( TypeRegistrar<T>::GetAnyName(), new IT<T>( toCreate ) );
}
template<typename T> T Get()
{
assert( TypeRegistrar<T>::HasName( typeName ) );
return return *static_cast<T const*>( data->Get() )
}
template<typename T>
class TypeRegistrar {
static TypeRegistrar* first; // = 0
TypeRegistrar* next;
std::string name;
TypeRegistrar( const TypeRegistrar& ); void operator=( const TypeRegistrar& ); // тел нетpublic:
TypeRegistrar( const char* TypeName ) : name( TypeName )
{
assert( name != 0 );
next = first;
first = this;
}
static bool HasName( const char* name )
{
assert( name != 0 );
for( TypeRegistrar* cur = fisrt; cur != 0; cur = cur->next ) {
if( cur->name == name ) {
return true;
}
}
return false;
}
static const char* GetAnyName()
{
assert( first != 0 );
return first->name.c_str();
}
};
private:
struct I {
virtual ~I() {}
virtual const void* Get() = 0;
virtual I* Clone() = 0;
};
template<typename T> struct IT : I {
virtual ~I() {}
virtual const void* Get() { return &data; }
virtual I* Clone() { return new IT( *this ); }
private:
T data;
};
const char* typeName;
I* data
};
Ну и пишешь дальше вне всех функций, например там, где объявляешь MyType, ещё и так:
MyAny::TypeRegistrar<MyType> registrarMyType( "MyType" ); // для этого можно сделать макрос
А потом в какой-то функции пишешь:
void foo( MyType& x )
{
SendMyMessage( MessageId, MyAny::Create( x ) );
}
а в обработчике пишешь:
void processMyMessage( MyAny any )
{
MyType t = any.Get<MyType>();
// текст обработчика
}
AM>А вообще, потребность в этом не ошибка проектирования?
Ну это от задачи зависит. иногда плагинам нужен сколь угодно расширяемый интерфейс, например.
Зачем тебе вообще сообщения понадобились?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
AM>Возникли вопросы про сабжу. Как это можно сделать и вообще правильно ли это? По мне так такая штука очень полезная, например есть набор модулей с общим интерфейсом, которые общаются через сообщения, но данные в сообщениях могут быть разного типа и их количество(типов данных) может со временем расти или в том же обзёрвере в его push варианте.
Я выскажу свое имхо. Как правило, с переданным объектом надо что-то делать, правильно? Вот это что-то напрашивается реализовать в виде виртуальных функций и передавать ссылку (указатель) на общего предка.
Здравствуйте, Vamp, Вы писали:
V>Я выскажу свое имхо. Как правило, с переданным объектом надо что-то делать, правильно? Вот это что-то напрашивается реализовать в виде виртуальных функций и передавать ссылку (указатель) на общего предка.
это работает если клиенты зафиксированы и вызывают функции только из определённого набора. когда новые функции могут появляться и на серверной, и на клиентской стороне, это не работает
BZ>это работает если клиенты зафиксированы и вызывают функции только из определённого набора. когда новые функции могут появляться и на серверной, и на клиентской стороне, это не работает
Не понял, ясное дело, если появляются новые функции, надо добавлять функции к коду класса, и добавлять код их вызова к коду клиента. А как иначе?
Здравствуйте, Vamp, Вы писали:
BZ>>это работает если клиенты зафиксированы и вызывают функции только из определённого набора. когда новые функции могут появляться и на серверной, и на клиентской стороне, это не работает V>Не понял, ясное дело, если появляются новые функции, надо добавлять функции к коду класса, и добавлять код их вызова к коду клиента. А как иначе?
а так, что при этом придётся выкинуть существующих клиентов и ждать пока появятся новые. что приемлемо, если это всё — часть одной программы и клиентов немного, но когда тебе нужно поменять десяток классов только потому что в одном из них появился новый параметр у метода — начинаешь понимать, что тут что-то не так
Здравствуйте, Vamp, Вы писали:
BZ>>а так, что при этом придётся выкинуть существующих клиентов и ждать пока появятся новые. V>Не понял, зачем выкидывать клиентов-то?
затем, что они несовместимы со старым интерфейсом, в который добавлен новый параметр