Одним из самых полезных для меня качеств языка C++ является, имхо, удобство при сопровождении и расширении ранее написанного кода. Этому способствуют такие возможности, как препроцессор, перегрузка методов и функций, аргументы по умолчанию и шаблоны. Вот как раз о шаблонах для упрощения сопровождения кода и пойдет речь.
Не хотелось бы превращать эту тему в очередной холивар по поводу C++ vs C# vs Java. Просто я на днях с помощью шаблонов расширил функциональность одного из модулей. При этом не потребовалась модификация кода, который работал с этим модулем ранее. Ниже я привожу примененное мной решение на C++.
Я хотел бы узнать, можно ли подобные вещи вытворять с помощью generic-ов в C# и/или Java. И если можно, насколько это выглядит проще/читабельнее и/или эффективнее.
Задача в том, что есть два механизма доставки получателям сообщений. Один синхронный (под названием mbapi), второй -- асинхронный (под названием SObjectizer). Был создан модуль, позволяющий строить преобразования синхронных сообщений mbapi в асинхронные сообщения SObjectizer-а. Для этого, каждому сообщению mbapi нужно было сопоставить специальный объект-почтальон (postman), который преобразовывает экземпляр асинхронного mbapi-сообщения, в экземпляр асинхронного сообщения и производит отсылку SObjectizer-сообщения получателю.
Для удобства использования всей этой кухни было сделано несколько вспомогательных шаблонных класса. Один из них, so_postman_templ_t был базовым шаблоном для почтальона. Он требовал, чтобы его параметризовали типом сообщения, у которого есть конструктор с тремя аргументами:
Sobj_type(
// Адрес получателя сообщения.
To_type * to,
// Само сообщение.
Mbapi_type * msg,
// Адрес отправителя сообщения (обратный адрес).
Reply_to_type * reply_to );
За время работы с so_postman_templ_t было написано много кода. Часть сообщений, которыми параметризовался so_postman_templ_t были созданы с использованием другого шаблона (so_msg_templ_t -- специальный шаблон для генерации SObjectizer-сообщений из mbapi-сообщений, so_msg_templ_t так же был подобным прозрачным образом модифицирован), но была и часть, которая была специально создана в расчете на первоначальную спецификацию.
Важно отметить, что сперва требовалось оперировать всего тремя параметрами сообщения: адресами отправителя и получателя, и самим сообщением. Но в один прекрасный момент, когда по старой схеме работало уже много чего, возникла необходимость сопоставить сообщению дополнительную, расширенную информацию. Т.е., потребовалось поддержать SObjectizer-сообщения, конструктор которых имеет вид:
т.е., в зависимости от того, какие параметры шаблона использованы для инстанцирования so_postman_templ_t, код so_postman_templ_t должен создавать объекты типа Sobj_type используя либо конструктор с тремя параметрами, либо конструктор с четырьмя параметрами. Причем, в коде so_postman_templ_t нельзя было написать простой if и использовать в разных его ветках разные вызовы конструкторов. Ведь тогда компилятор и линкер требовали бы наличия у типа Sobj_type сразу обоих конструкторов. А вся задача состояла в том, чтобы новая версия so_postman_templ_t умела работать со старыми типами, у которых конструкторы всего с тремя параметрами.
У меня на C++ шаблонах получилось приведенное ниже решение. Хочу сделать два отступления. Во-первых, требовалось обеспечить совместимость с VC++ 6.0, в котором нет поддержки частичной специализации. Из-за этого пришлось ввести дополнительные типы: sender_params_t, no_extended_info_support_sender_t, extended_info_support_sender_t, sender_selector_t. Если бы можно было применить частную специализацию, то можно было обойтись всего одним типом и его частичной специализацией. И решение бы сильно сократилось в объеме.
Во-вторых, я понимаю, что ошибка была допущена в самом начале, когда было решено передавать в конструктор Sobj_type всю имеющуюся информацию в виде отдельных параметров. Проблем бы не было, если бы передавался всего один аргумент -- некая структура, из которой можно было бы извлечь все. Да, такая ошибка была мной допущена. Но, она была выявлена после двух лет успешной эксплуатации, когда начали меняться требования к имеющемуся механизму. И вот в таких неприятных, но реальных, условиях шаблоны C++ позволили обеспечить работоспособность уже написанного кода.
Вот, собственно, само решение:
//
// extended_info_t
//
/*!
\since v.3.3.0
\brief Расширеная информация об MBAPI-сообщении, которая доступна
для получателя MBAPI-сообщения.
Большинству получателей MBAPI-сообщений необходимо всего три значения:
пункт назначения сообщения, само сообщение и его обратный адрес.
Но в версии 3.3.0 получателю предоставляется так же возможность получить
историю ремаршрутизаций сообщения. В следующих версиях получатель может
захотеть получать еще какую-то информацию о сообщении. Для того, чтобы
не модифицировать для этого шаблон so_templ_postman_t предлагается
всю дополнительную информацию передавать получателю в виде структуры
extended_info_t, которая будет расширятся дополнительными атрибутами по
мере надобности.
*/struct extended_info_t
{
//! История ремаршрутизации сообщений.
std::auto_ptr< rerouting_history_t > m_rerouting_history;
//! Инициализирующий конструктор.
/*!
Самостоятельно извлекает всю информацию из delivery_info_t.
*/
extended_info_t( const delivery_info_t & info )
: m_rerouting_history( info.rerouting_history() ?
new rerouting_history_t( *info.rerouting_history() ) : 0 )
{}
~extended_info_t()
{}
/*! \name Методы getter-ы.
\{ */const rerouting_history_t *
rerouting_history() const { return m_rerouting_history.get(); }
/*! \} */
};
namespace impl
{
//
// sender_params_t
//
/*!
\since v.3.3.0
\brief Вспомогательная обертка для параметров доставки MBAPI-сообщения
в виде SObjectizer сообщения.
Необходима, главным образом, для того, чтобы сделать вспомогательные
классы no_extended_info_support_sender_t и extended_info_support_sender_t
не шаблонными -- это необходимо для поддержки C++ компилятров, в которых
нет частичной специализации шаблонов.
*/template<
class Mbapi_type,
class Sobj_type,
class To_type,
class Reply_to_type >
struct sender_params_t
{
/*! \name Псевдонимы для параметров шаблона.
\{ */typedef Mbapi_type mbapi_type_t;
typedef Sobj_type sobj_type_t;
typedef To_type to_type_t;
typedef Reply_to_type reply_to_type_t;
/*! \} */
//! Исходные параметры MBAPI-сообщения.const delivery_info_t & m_info;
//! Инициализирующий конструктор.
sender_params_t( const delivery_info_t & info )
: m_info( info )
{}
//! Доступ к параметрам MBAPI-сообщения.const delivery_info_t &
info() const { return m_info; }
};
//
// no_extended_info_support_sender_t
//
/*!
\since v.3.3.0
\brief Генератор SObjectizer-сообщения, для случая, когда конструктор
сообщения получает всего три параметра.
*/struct no_extended_info_support_sender_t
{
//! Шаблонный метод для генерации SObjectizer-сообщения.
/*!
\note В качестве параметра шаблона ожидается sender_params_t.
*/template< class Params >
void
send(
//! Имя владельца сообщения.const std::string & owner_name,
//! Имя SObjectizer-сообщения.const std::string & msg_name,
//! Все доступные параметры MBAPI-сообщения.const Params & p ) const
{
// Для защиты от исключений при выделении памяти.
std::auto_ptr< Params::to_type_t > to(
dynamic_cast< typename Params::to_type_t * >(
p.info().to().clone() ) );
std::auto_ptr< Params::mbapi_type_t > msg(
dynamic_cast< typename Params::mbapi_type_t * >(
p.info().msg().clone() ) );
std::auto_ptr< Params::reply_to_type_t > reply_to(
dynamic_cast< typename Params::reply_to_type_t * >(
p.info().reply_to().clone() ) );
so_4::api::send_msg_safely( owner_name, msg_name,
new typename Params::sobj_type_t(
to.release(),
msg.release(),
reply_to.release() ),
"", p.info().delay() );
}
};
//
// extended_info_support_sender_t
//
/*!
\since v.3.3.0
\brief Генератор SObjectizer-сообщения, для случая, когда конструктор
сообщения получает четвертым параметром rerouting_history.
*/struct extended_info_support_sender_t
{
//! Шаблонный метод для генерации SObjectizer-сообщения.
/*!
\note В качестве параметра шаблона ожидается sender_params_t.
*/template< class Params >
void
send(
//! Имя владельца сообщения.const std::string & owner_name,
//! Имя SObjectizer-сообщения.const std::string & msg_name,
//! Все доступные параметры MBAPI-сообщения.const Params & p ) const
{
// Для защиты от исключений при выделении памяти.
std::auto_ptr< Params::to_type_t > to(
dynamic_cast< typename Params::to_type_t * >(
p.info().to().clone() ) );
std::auto_ptr< Params::mbapi_type_t > msg(
dynamic_cast< typename Params::mbapi_type_t * >(
p.info().msg().clone() ) );
std::auto_ptr< Params::reply_to_type_t > reply_to(
dynamic_cast< typename Params::reply_to_type_t * >(
p.info().reply_to().clone() ) );
std::auto_ptr< extended_info_t > extended(
new extended_info_t( p.info() ) );
so_4::api::send_msg_safely( owner_name, msg_name,
new typename Params::sobj_type_t(
to, msg, reply_to, extended ),
"", p.info().delay() );
}
};
//
// sender_selector_t
//
/*!
\since v.3.3.0
\brief Вспомогательный класс для выбора одного из sender-ов.
Данная версия одновременно является специализацией для случая,
когда Extended_info_supported == false.
*/template< bool Extended_info_supported >
struct sender_selector_t
{
typedef no_extended_info_support_sender_t sender_type_t;
};
/*!
\since v.3.3.0
Специализация sender_selector_t для случая, когда
Extended_info_suppored == true.
*/template<>
struct sender_selector_t< true >
{
typedef extended_info_support_sender_t sender_type_t;
};
} /* namespace impl */
//
// so_postman_templ_t
//
//! Реализация интерфейса %postman_t в виде шаблона.
/*!
\par Версии, предшествующие 3.3.0
Предназначен для генерации SObjectizer-сообщений,
которые имеют конструктор следующего вида:
\code
Sobj_type(
To_type * to,
Mbapi_type * msg,
Reply_to_type * reply_to );
\endcode
Например:
\code
struct msg_receiver_ack_t
{
std::auto_ptr< mbapi_3::dest_t > m_to;
std::auto_ptr< mbapi_3::sms::receive_ack_t > m_msg;
std::auto_ptr< mbapi_3::dest_t > m_reply_to;
msg_receiver_ack_t() {}
msg_receiver_ack_t(
mbapi_3::dest_t * to,
mbapi_3::sms::receive_ack_t * msg,
mbapi_3::dest_t * reply_to )
: m_to( to )
, m_msg( msg )
, m_reply_to( reply_to )
{
}
};
so_postman_templ_t<
mbapi_3::sms::receive_ack_t,
msg_receive_ack_t >
receive_ack_postman(
"some_agent_name",
"msg_receive_ack" );
\endcode
\par Начиная с версии 3.3.0 поддерживаются SObjectizer-сообщения,
конструктор который имеет следующий вид:
\code
Sobj_type(
std::auto_ptr< To_type > to,
std::auto_ptr< Mbapi_type > msg,
std::auto_ptr< Reply_to_type > reply_to,
std::auto_ptr< mbapi_3::extended_info_t > extended_info );
\endcode
*/template<
// Тип MBAPI-сообщения.class Mbapi_type,
// Тип SObjectizer-сообщения.class Sobj_type,
// Тип адреса получателя сообщения.class To_type = mbapi_3::dest_t,
// Тип адреса отправителя сообщения.class Reply_to_type = mbapi_3::dest_t,
// Поддерживается ли получателем расширенная информация?bool Extended_info_supported = false >
class so_postman_templ_t :
public postman_t
{
private :
//! Имя агента-владельца сообщения.const std::string m_owner_agent;
//! Имя сообщения, которое нужно отослать.const std::string m_msg_name;
//! Объект, который реально будет осущетвлять доставку сообщения.const typename impl::sender_selector_t<
Extended_info_supported >::sender_type_t
m_sender;
public :
inline
so_postman_templ_t(
//! Имя агента-владельца сообщения.const std::string & owner_agent,
//! Имя сообщения, которое нужно отослать.const std::string & msg_name )
: m_owner_agent( owner_agent )
, m_msg_name( msg_name )
{}
virtual ~so_postman_templ_t()
{}
virtual void
deliver(
const mbapi_3::router::delivery_info_t & info )
{
impl::sender_params_t<
Mbapi_type, Sobj_type, To_type, Reply_to_type >
params( info );
m_sender.send( m_owner_agent, m_msg_name, params );
}
};
} /* namespace router */
} /* namespace mbapi_3 */
Т.е., фокус в том, что если Extended_info_supported равен false (по умолчанию), то so_postman_templ_t::m_sender имеет тип impl::no_extended_info_support_sender_t и для инстанцирования SObjectizer-сообщения используется конструктор с тремя параметрами. В противном случае m_sender имеет тип impl::extended_info_support_sender_t и для инстанцирования SObjectizer сообщения используется контруктор с четырьмя параметрами. Причем осуществляется все это в compile-time.
Получается, что в старом коде и в том новом коде, в котором не требуется extended_info_t, шаблон so_postman_templ_t применяется без последного параметра Extended_info_supported (принимается значение false). Если же новому коду нужно использовать extended_info_t, то при инстанцировании so_postman_templ_t указывается true в качестве параметра Extended_info_supported.
Важным достоинством шаблонов C++ здесь является то, что если extended_info_t получателю сообщения не нужен, то при генерации SObjectizer сообщения он даже не создается.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Уважаемые коллеги!
E> Одним из самых полезных для меня качеств языка C++ является, имхо, удобство при сопровождении и расширении ранее написанного кода. Этому способствуют такие возможности, как препроцессор, перегрузка методов и функций, аргументы по умолчанию и шаблоны. Вот как раз о шаблонах для упрощения сопровождения кода и пойдет речь.
E> Не хотелось бы превращать эту тему в очередной холивар по поводу C++ vs C# vs Java. Просто я на днях с помощью шаблонов расширил функциональность одного из модулей. При этом не потребовалась модификация кода, который работал с этим модулем ранее. Ниже я привожу примененное мной решение на C++.
[...skipped...]
Правильно я догадался, что при появлении необходимости передавать _иногда_ дополнительную информацию, пришлось не только расширить so_postman_templ_t, но и добавить несколько типов: no_extended_info_support_sender_t, extended_info_support_sender_t, template< bool Extended_info_supported >
struct sender_selector_t, template<> struct sender_selector_t< true >?
Пытаюсь вот представить себе, что было и что стало.
under « — Tango In Space»,
... << RSDN@Home 1.1.4 beta 7 rev. 461>>
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>Здравствуйте, eao197, Вы писали:
E>>Уважаемые коллеги!
E>> Одним из самых полезных для меня качеств языка C++ является, имхо, удобство при сопровождении и расширении ранее написанного кода. Этому способствуют такие возможности, как препроцессор, перегрузка методов и функций, аргументы по умолчанию и шаблоны. Вот как раз о шаблонах для упрощения сопровождения кода и пойдет речь.
E>> Не хотелось бы превращать эту тему в очередной холивар по поводу C++ vs C# vs Java. Просто я на днях с помощью шаблонов расширил функциональность одного из модулей. При этом не потребовалась модификация кода, который работал с этим модулем ранее. Ниже я привожу примененное мной решение на C++.
_FR>[...skipped...]
_FR> _FR>Правильно я догадался, что при появлении необходимости передавать _иногда_ дополнительную информацию, пришлось не только расширить so_postman_templ_t, но и добавить несколько типов: no_extended_info_support_sender_t, extended_info_support_sender_t, template< bool Extended_info_supported > _FR>struct sender_selector_t, template<> struct sender_selector_t< true >? _FR>Пытаюсь вот представить себе, что было и что стало.
Да, правильно, эти типы пришлось добавить (вообще, все что было добавлено, помечено в комментариях как \since v.3.3.0)
Предыдущая реализация so_postman_templ_t имела вид:
template<
// Тип MBAPI-сообщения.class Mbapi_type,
// Тип SObjectizer-сообщения.class Sobj_type,
// Тип адреса получателя сообщения.class To_type = mbapi_3::dest_t,
// Тип адреса отправителя сообщения.class Reply_to_type = mbapi_3::dest_t >
class so_postman_templ_t :
public postman_t
{
private :
//! Имя агента-владельца сообщения.const std::string m_owner_agent;
//! Имя сообщения, которое нужно отослать.const std::string m_msg_name;
public :
inline
so_postman_templ_t(
//! Имя агента-владельца сообщения.const std::string & owner_agent,
//! Имя сообщения, которое нужно отослать.const std::string & msg_name )
:
m_owner_agent( owner_agent ),
m_msg_name( msg_name )
{
}
virtual ~so_postman_templ_t()
{
}
inline
virtual void
deliver(
const mbapi_3::dest_t & to,
const mbapi_3::msg_t & msg,
const mbapi_3::dest_t & reply_to,
unsigned int delay )
{
so_4::rt::msg_auto_ptr_t< Sobj_type >
msg_to_send( new Sobj_type(
dynamic_cast< To_type * >( to.clone() ),
dynamic_cast< Mbapi_type * >( msg.clone() ),
dynamic_cast< Reply_to_type * >( reply_to.clone() ) )
);
msg_to_send.send( m_owner_agent, m_msg_name, "",
delay );
}
};
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Все это здорово, но ради единственно энтузиазма продираться сквозь килобайты кода имхо вряд ли кто станет. Если ты хочешь получить ответ, думаю стоит попытаться вырпазить вопрос ввиде десятка другого строк.
Здравствуйте, AndrewVK, Вы писали:
AVK>Все это здорово, но ради единственно энтузиазма продираться сквозь килобайты кода имхо вряд ли кто станет. Если ты хочешь получить ответ, думаю стоит попытаться вырпазить вопрос ввиде десятка другого строк.
Попробуем.
Итак, был класс:
template< class Mbapi_type, class Sobj_type, class To_type = mbapi_3::dest_t, class Reply_to_type = mbapi_3::dest_t >
class so_postman_templ_t :
public postman_t
{
private :
......
inline
virtual void
deliver(
const mbapi_3::dest_t & to,
const mbapi_3::msg_t & msg,
const mbapi_3::dest_t & reply_to,
unsigned int delay )
{
so_4::rt::msg_auto_ptr_t< Sobj_type >
msg_to_send( new Sobj_type(
dynamic_cast< To_type * >( to.clone() ),
dynamic_cast< Mbapi_type * >( msg.clone() ),
dynamic_cast< Reply_to_type * >( reply_to.clone() ) )
);
msg_to_send.send( m_owner_agent, m_msg_name, "",
delay );
}
};
Важно то, что метод so_postman_templ_t::deliver вызывал у типа Sobj_type конструктор с тремя параметрами. Который использовался, например, так:
Со временем пришлось добавить в некоторых случаях поддержку дополнительной информации, для чего у типов Sobj_type (которые хотели эту дополнительную информацию получить) должен быть конструктор с дополнительным аргументом:
При этом требовалось чтобы so_postman_templ_t можно было параметризовать как типом, в конструктор которого принимает три агрумента, так и типом, который принимает четыре аргумента:
При этом нельзя было в коде so_postman_templ_t написать что-то вроде:
template<
class Mbapi_type, class Sobj_type, class To_type = mbapi_3::dest_t, class Reply_to_type = mbapi_3::dest_t,
bool Extended_info_supported = false >
class so_postman_templ_t :
public postman_t
{
private :
......
inline
virtual void
deliver(
const mbapi_3::dest_t & to,
const mbapi_3::msg_t & msg,
const mbapi_3::dest_t & reply_to,
unsigned int delay )
{
if( Extended_info_supported )
new Sobj_type( /* четыре аргумента */ );
else
new Sobj_type( /* три аргумента */ );
...
}
};
Поскольку мне нужно было поддерживать Visual C++, в котором нет частичной специализации, то пришлось сделать сложное решение с несколькими вспомогательными типами:
namespace impl
{
template<
class Mbapi_type,
class Sobj_type,
class To_type,
class Reply_to_type >
struct sender_params_t
{
typedef Mbapi_type mbapi_type_t;
typedef Sobj_type sobj_type_t;
typedef To_type to_type_t;
typedef Reply_to_type reply_to_type_t;
...
};
struct no_extended_info_support_sender_t
{
template< class Params >
void
send(
const std::string & owner_name,
const std::string & msg_name,
const Params & p ) const
{
/* Фактически здесь вызывается: */new Sobj_type( /* три параметра */ );
...
}
};
struct extended_info_support_sender_t
{
template< class Params >
void
send(
const std::string & owner_name,
const std::string & msg_name,
const Params & p ) const
{
/* Фактически здесь вызывается: */new Sobj_type( /* четыре параметра */ );
...
}
};
template< bool Extended_info_supported >
struct sender_selector_t
{
typedef no_extended_info_support_sender_t sender_type_t;
};
template<>
struct sender_selector_t< true >
{
typedef extended_info_support_sender_t sender_type_t;
};
} /* namespace impl */template<
class Mbapi_type,
class Sobj_type,
class To_type = mbapi_3::dest_t,
class Reply_to_type = mbapi_3::dest_t,
bool Extended_info_supported = false >
class so_postman_templ_t :
public postman_t
{
private :
/* Вот здесь на этапе компиляции определяется, будет ли вызываться
конструктор с тремя параметрами или конструктор с четырьмя параметрами */const typename impl::sender_selector_t<
Extended_info_supported >::sender_type_t
m_sender;
...
public :
virtual void
deliver(
const mbapi_3::router::delivery_info_t & info )
{
impl::sender_params_t<
Mbapi_type, Sobj_type, To_type, Reply_to_type >
params( info );
m_sender.send( m_owner_agent, m_msg_name, params );
}
};
Если бы можно было забить на Visual C++ 6.0, то нужно было бы всего лишь сделать частную специализацию so_postman_templ_t для случая, когда Extended_info_supported == true.
А воопрос вообще занимает одну строку: чтобы потребовалось сделать для повторения такого фокуса в C# и/или Java?
PS
Здесь приведен псевдокод. Реальный код и более подробная постановка задачи изложена в исходном посте.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
eao197,
> Для удобства использования всей этой кухни было сделано несколько вспомогательных шаблонных класса. Один из них, so_postman_templ_t был базовым шаблоном для почтальона. Он требовал, чтобы его параметризовали типом сообщения, у которого есть конструктор с тремя аргументами: >
> Sobj_type(
> // Адрес получателя сообщения.
> To_type * to,
> // Само сообщение.
> Mbapi_type * msg,
> // Адрес отправителя сообщения (обратный адрес).
> Reply_to_type * reply_to );
>
Тпру, дальше дорогу не сделали: generics не умеют "сами" создавать объекты классов с конструкторами, имеющими параметры, поэтому с использованием generics исходный код выглядел бы как-нибудь очень по-другому.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>eao197,
>> Для удобства использования всей этой кухни было сделано несколько вспомогательных шаблонных класса. Один из них, so_postman_templ_t был базовым шаблоном для почтальона. Он требовал, чтобы его параметризовали типом сообщения, у которого есть конструктор с тремя аргументами: >>
>> Sobj_type(
>> // Адрес получателя сообщения.
>> To_type * to,
>> // Само сообщение.
>> Mbapi_type * msg,
>> // Адрес отправителя сообщения (обратный адрес).
>> Reply_to_type * reply_to );
>>
ПК>Тпру, дальше дорогу не сделали: generics не умеют "сами" создавать объекты классов с конструкторами, имеющими параметры, поэтому с использованием generics исходный код выглядел бы как-нибудь очень по-другому.
Т.е. при использовании generic-ов пришлось бы, например, чтобы в типе SObj_type был статический метод-фабрика. И создание бы объекта выглядело как-то так:
Sobj_type * instance = Sobj_type::create_instance( to, msg, reply_to );
Такая штука с generic-ами возможна?
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
eao197,
> ПК> Тпру, дальше дорогу не сделали: generics не умеют "сами" создавать объекты классов с конструкторами, имеющими параметры, поэтому с использованием generics исходный код выглядел бы как-нибудь очень по-другому.
> Т.е. при использовании generic-ов пришлось бы, например, чтобы в типе SObj_type был статический метод-фабрика. И создание бы объекта выглядело как-то так: >
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Так тоже не получится: generics умеют вызывать только (виртуальные) методы интерфейсов. Вот мои мучения вокруг этого: http://rsdn.ru/Forum/Message.aspx?mid=979456
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Так тоже не получится: generics умеют вызывать только (виртуальные) методы интерфейсов. Вот мои мучения вокруг этого: http://rsdn.ru/Forum/Message.aspx?mid=979456
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>eao197,
>> Т.е. при использовании generic-ов пришлось бы, например, чтобы в типе SObj_type был статический метод-фабрика. И создание бы объекта выглядело как-то так: >>
>> Такая штука с generic-ами возможна?
ПК>Так тоже не получится: generics умеют вызывать только (виртуальные) методы интерфейсов. Вот мои мучения вокруг этого: http://rsdn.ru/Forum/Message.aspx?mid=979456
Имхо, мне показалось, что там речь шла немного о другом -- о том, насколько дорого достичь приемлимой производительности с использованием generic-ов в C#. Мне же интересен немного другой аспект generic-ов, я бы сказал, выразительная мошь. Т.е. меня интересует, насколько геморройно (или наоборот) с помощью generic-ов решаются задачи, которые я решал на шаблонах C++ почти без проблем.
Нужно, похоже, самому разобраться с generic-ами в Java (поскольку этот язык я когда-то знал), а затем и как-то с C# (только его я совсем не знаю). И тогда, может быть, что-то проясниться.
Пока я подумал, что на generic-ах можно было бы сделать что-то, похожее на этот вариант на C++:
template< class Mbapi_type, class Sobj_type, class Sobj_type_factory, class To_type, class Reply_to_type >
class so_postman_templ_t
{
private :
Sobj_type_factory m_factory;
...
public :
so_postman_templ_t( Sobj_type_factory factory, ... ) : m_factory( factory ), ... { ... }
void deliver(...)
{
Sobj_type * message = Sobj_type_factory.create(...);
...
}
};
Да только засомневался что-то, что такое возможно, поскольку Sobj_type_factory так же должен быть шаблоном (ведь он же должен как-то связываться с параметрами Mbapi_type, Sobj_type, To_type, Reply_to_type).
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Delegates — типичное решение для этого ограничения generics. В C# они могут быть анонимными. Конечно, будет runtime cost и всё такое, но это уже другой вопрос.
А вообщем, имеет мало смысла пытаться перевести код с templates на generics по причине их фундаментальных различий. Нужно с нуля его разрабатывать, учитывая возможности и ограничения generics.
"McSeem2" <12737@users.rsdn.ru> wrote in message news:1201590@news.rsdn.ru > Bottom line: > Генерики, что в C#, что в Java — это чисто так, гламурненько. А > шаблоны в C++ — это ГОТИЧНО!
Здравствуйте, alexeiz, Вы писали:
A>"Павел Кузнецов" <5834@users.rsdn.ru> wrote in message A>news:1201223@news.rsdn.ru >> Так тоже не получится: generics умеют вызывать только (виртуальные) >> методы интерфейсов. Вот мои мучения вокруг этого: >> http://rsdn.ru/Forum/Message.aspx?mid=979456
A>Delegates — типичное решение для этого ограничения generics. В C# они могут быть анонимными. Конечно, будет runtime cost и всё такое, но это уже другой вопрос.
A>А вообщем, имеет мало смысла пытаться перевести код с templates на generics по причине их фундаментальных различий. Нужно с нуля его разрабатывать, учитывая возможности и ограничения generics.
Ну это я уже начал понимать. А как нужное мне решение может выглядеть на generic-ах?
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Признаю, что в исходном посте я сделал слишком объемное и сложное описание. Приношу свои извинения.
Попробую сделать его попроще.
Итак, существует некий механизм доставки сообщений получателям. В этом механизме есть:
// Базовый класс для сообщений.class msg_t { ... };
// Базовый класс для пунктов назначений (адресов получателей и отправителей сообщений).class dest_t { ... };
// Метод для маршрутизации сообщения:void
route(
// Куда должно идти сообщение.const dest_t & to,
// Само сообщение.const msg_t & msg,
// От кого идет сообщение.const dest_t & reply_to,
// Еще какие-то параметры, суть которых сейчас не важна
... );
Если кому-то нужно было отослать сообщение, то выполнялось это просто, например, так:
А вот с получением сообщения было сложнее. Каждый, кто хотел получать сообщения, должен был подписаться на него и зарегистрировать специальный объект-почтальон. Метод route пробегался по всем зарегистрированным получателям и, если находил нужного, то передавал сообщение почтальону.
Почтальоны были двух видов: синхронные, которые сразу инициировали обработку сообщения внутри route, и асинхронные, которые ставили сообщение в очередь объектам-агентам, а сообщение обрабатывалось тогда, когда агент разгребал свою очередь и извлекал оттуда сообщение. Далее речь пойдет именно об асинхронных почтальонах.
Задача асинхронного почтальона в том, чтобы преобразовать тройку значений <to, msg, reply_to> в один динамически созданный объект. Казалось бы, что здесь можно было бы обойтись готовой структурой:
и сделать одного почтальона, который бы генерировал объекты so_message_t. Но, проблема в том, что агенты хотят работать с конкретными типами пунктов назначений и сообщений. Т.е., чтобы so_message_t было шаблоном:
template< class Msg, class To, class Reply_to >
struct so_msg_templ_t
{
std::auto_ptr< To > m_to;
std::auto_ptr< Msg > m_msg;
std::auto_ptr< Reply_to > m_reply_to;
};
А почтальон бы автоматически приводил бы указатели от базовых типов (dest_t, msg_t) к производным типам (To, Msg, Reply_to).
Собственно такой шаблон сообщения и такой шаблон почтальона был создан. Более того, шаблон почтальона был сделан так, чтобы он не был строго завязан на тип so_msg_templ_t -- реальный тип сообщения задавался параметром. Такая схема отлично работала несколько лет. Но в коде шаблона почтальона был явно зашит вызов конструктора сообщения:
so_postman_templ_t::deliver( /* параметры из route */ )
{
... new Message_type( to.clone(), msg.clone(), reply_to.clone() ) ...
}
Причем конструктора с тремя параметрами!
За время работы с so_postman_templ_t было сделано много кода и в качестве сообщений для агентов использовались не только инстанции шаблона so_msg_templ_t, но и не шаблонные типы сообщения. Последнее очень важно, т.к. у класса so_msg_templ_t можно изменить конструктор (что и было сделано), а вот у не шаблонных объектов-сообщений сделать это тяжело. И, что еще важно, где-то so_postman_templ_t использовался напрямую, а где-то применялись производные от него типы (так же шаблонные).
Итак, в один прекрасный день потребовалось поддержать сообщения, которые кроме трех основных параметров должны были получать еще один. И встала задача переделать класс so_postman_templ_t так, чтобы он мог либо вызывать конструктор с тремя аргументами, либо конструктор с четырьмя аргументами. Т.е. что-то вроде:
so_postman_templ_t::deliver( /* параметры из route */ )
{
if( Extended_info_supported )
// Используем конструктор с четырьмя параметрами.
... new Message_type( to.clone(), msg.clone(), reply_to.clone(), extended_info.clone() ) ...
else// Используем конструктор с тремя параметрами.
... new Message_type( to.clone(), msg.clone(), reply_to.clone() ) ...
}
Где Extended_info_suppored -- это новый параметр шаблона, который по-умолчанию устанавливается в false.
Но именно так записать нельзя, т.к. тогда бы компилятор требовал бы у Message_type обоих конструкторов.
Поэтому пришлось искать решение через специализацию. Не нужна была бы поддержка VC++6.0, то можно было бы сделать частную специализацию so_postman_templ_t для случая, когда Extended_info_supported == true. Но поддерживать VC++6.0 потребовалось, поэтому пришлось использовать вспомогательные типы:
struct no_extended_info_support_sender_t
{
// Используем конструктор с тремя параметрами.
... new Message_type( to.clone(), msg.clone(), reply_to.clone() ) ...
};
struct extended_info_support_sender_t
{
// Используем конструктор с четырьмя параметрами.
... new Message_type( to.clone(), msg.clone(), reply_to.clone(), extended_info.clone() ) ...
};
template< bool Extended_info_supported >
struct sender_selector_t
{
typedef no_extended_info_support_sender_t sender_type_t;
};
template<>
struct sender_selector_t< true >
{
typedef extended_info_support_sender_t sender_type_t;
};
Вот как раз тип sender_selector_t и его полная специализация для Extended_info_supported==true и определяли, какой конструктор Message_type будет использоваться:
template< class Mbapi_type, class Sobj_type, class To_type = mbapi_3::dest_t, class Reply_to_type = mbapi_3::dest_t,
bool Extended_info_supported = false >
class so_postman_templ_t : public postman_t {
...
//! Объект, который реально будет осущетвлять доставку сообщения.const typename impl::sender_selector_t<
Extended_info_supported >::sender_type_t
m_sender;
...
};
Если Extended_info_supported == false, то у объекта m_sender был тип no_extended_info_support_sender_t и у Message_type вызывался конструктор с тремя параметрами. В противном случае у m_sender был тип extended_info_support_sender_t и у Message_type вызывался конструктор с четырьмя параметрами.
Благодоря фокусу с параметром шаблона по-умолчанию Extended_info_supported весь старый код, в котором было написано что-то типа:
new so_postman_templ_t< some_message_t, some_async_message_t, some_dest_t >( ... );
остался полностью работоспособным.
Надеюсь, что это описание будет доступнее. Полный же код реализованного решения приведен в исходном сообшении.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали: E>Признаю, что в исходном посте я сделал слишком объемное и сложное описание. Приношу свои извинения. E>Попробую сделать его попроще.
Спасибо. Я правильно понял, что суть проблемы сводится к следующему:
1. У нас был универсальный класс нетипизированного сообщения
2. У нас был шаблонный класс типизированного сообщения, унаследованный от нетипизированного
3. У нас были нешаблонные классы-потомки нетипизированного сообшения
4. У всех классов были конструкторы с тремя параметрами, что использовалось внутри кода доставки для порождения новых сообщений.
Не вполне понятно, как выглядит теперь сигнатура метода Deliver и откуда брать четвертый параметр для сообщений, у которых его нет. Ну да ладно. Попробуем так угадать.
Итак, как бы это работало на C# 2.0?
Начнем с конца.
Поскольку дженерики не позволяют использовать никаких конструкторов вообще, кроме как конструктора без параметров, то нам пришлось бы придумывать какой-то другой метод создания объектов-сообщений.
Вариант 1 — использовать Reflection.
using System.Reflection;
public class Postman<MessageType>
{
public void Deliver(Destination to, Msg msd, Destination replyTo)
{
ConstructorInfo messageCtor = typeof(MessageType).GetConstructor(new Type[]{typeof(Destination),typeof(Msg), typeof(Destination)});
if (messageCtor != null)
MessageType msg = (MessageType)messageCtor.Invoke(new object[]{to, msg, replyTo};
}
}
В таком случае все, что нам надо от жизни для реализации ExtendedDeliver — это добавить пару строк:
public class Postman<MessageType>
{
public void ExtendedDeliver(Destination to, Msg msd, Destination replyTo, Extended extended)
{
MessageType msg = null;
ConstructorInfo messageCtor = typeof(MessageType).GetConstructor(new Type[]{typeof(Destination),typeof(Msg), typeof(Destination), typeof(Extended)});
if (messageCtor != null)
msg = (MessageType)messageCtor.Invoke(new object[]{to, msg, replyTo, extended};
else// попробуем найти старый конструктор
{
messageCtor = typeof(MessageType).GetConstructor(new Type[]{typeof(Destination),typeof(Msg), typeof(Destination)});
if (messageCtor != null)
msg = (MessageType)messageCtor.Invoke(new object[]{to, msg, replyTo};
}
}
}
Как видим, все получилось и намного компактнее, чем на C++.
Конечно, в реальной жизни такой подход скорее всего будет быстро соптимизирован — уж очень плохо вставлять постоянный поиск конструктора.
Поэтому на практике, для сообщений скорее всего будет применяться фабрика. И приплясываний с бубном не потребуется вообще.
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Sinclair,
> Вариант 1 — использовать Reflection. >
> public class Postman<MessageType>
> {
> public void Deliver(Destination to, Msg msd, Destination replyTo)
> {
> ConstructorInfo messageCtor = typeof(MessageType).GetConstructor(new Type[]{typeof(Destination),typeof(Msg), typeof(Destination)});
> if (messageCtor != null)
> MessageType msg = (MessageType)messageCtor.Invoke(new object[]{to, msg, replyTo};
> }
> }
>
Очень неплохо Есть, правда, кой-какие недостатки по сравнению с оригинальным решением (пережить, наверное, можно, но неприятно):
нет статической проверки типов, все смещается на время исполнения
насколько я понимаю, не будут работать конструкторы MessageType с неточным совпадением типов (например, принимающие ссылку на базовый класс)
если я не ошибаюсь, не будут работать шаблоны конструкторов MessageType
>
> public class Postman<MessageType>
> {
> public void ExtendedDeliver(Destination to, Msg msd, Destination replyTo, Extended extended)
> {
> MessageType msg = null;
> ConstructorInfo messageCtor = typeof(MessageType).GetConstructor(new Type[]{typeof(Destination),typeof(Msg), typeof(Destination), typeof(Extended)});
> if (messageCtor != null)
> msg = (MessageType)messageCtor.Invoke(new object[]{to, msg, replyTo, extended};
> else// попробуем найти старый конструктор
> {
> messageCtor = typeof(MessageType).GetConstructor(new Type[]{typeof(Destination),typeof(Msg), typeof(Destination)});
> if (messageCtor != null)
> msg = (MessageType)messageCtor.Invoke(new object[]{to, msg, replyTo};
> }
> }
> }
>
Но, вообще
> Как видим, все получилось и намного компактнее, чем на C++.
В C++, если бы eao197 не пришлось мучиться с VC++ 6.0, было бы не длиннее.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Очень неплохо Есть, правда, кой-какие недостатки по сравнению с оригинальным решением (пережить, наверное, можно, но неприятно): ПК> нет статической проверки типов, все смещается на время исполнения
Тут ничего не поделаешь. Можно было бы обойти этот момент, если бы была возможность требовать не только пустой new.
Максимум удобства, который можно получить — это выполнить поиск конструктора заранее, в статическом инициализаторе. И, соответственно, кинуть исключение. Тогда падение произойдет на очень раннем этапе (в случае удачи — прямо на старте приложения), и вся необходимая информация попадет именно к тому, кому нужно.
ПК> насколько я понимаю, не будут работать конструкторы MessageType с неточным совпадением типов (например, принимающие ссылку на базовый класс)
Хороший вопрос... Нет. В доке сказано, что дефолтный биндер, применяемый при поиске мемберов, учитывает все стандартные приведения:
The following table lists the conversions supported by the default binder.
Source Type | Target Type
------------------------
Any type | Its base type.
Any type | The interface it implements.
Char | Unt16, UInt32, Int32, UInt64, Int64, Single, Double
Byte | Char, Unt16, Int16, UInt32, Int32, UInt64, Int64, Single, Double
SByte | Int16, Int32, Int64, Single, Double
UInt16 | UInt32, Int32, UInt64, Int64, Single, Double
Int16 | Int32, Int64, Single, Double
UInt32 | UInt64, Int64, Single, Double
Int32 | Int64, Single, Double
UInt64 | Single, Double
Int64 | Single, Double
Single | Double
Non-reference | By-reference.
Кстати, похоже я прогнал. В примечаниях MSDN к Type.GetConstructor приведена такая фраза:
If the current Type represents a type parameter of a generic type or method, this method always returns null.
Что, вообще-то, очень странно. Впрочем, существует набор способов это обойти. ПК> если я не ошибаюсь, не будут работать шаблоны конструкторов MessageType
Это я не понял. Ты что имеешь в виду?
Я-то имел в виду вот что:
public class MessageType<To, Msg, ReplyTo>
where
To: Destination;
Msg: Messsage;
ReplyTo: Destination
{
public MessageType(To to, Msg msg, ReplyTo r) // типизированный конструктор
{
}
}
Именно этот конструктор мы и ищем. ПК>В C++, если бы eao197 не пришлось мучиться с VC++ 6.0, было бы не длиннее.
Может быть.
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
ПК>> нет статической проверки типов, все смещается на время исполнения
S>Тут ничего не поделаешь. Можно было бы обойти этот момент, если бы была возможность требовать не только пустой new.
Не очень понял, а как бы ты написал требования, если нужно выбрать один из двух конструкторов, а не требовать какой-либо из них?..
ПК>> если я не ошибаюсь, не будут работать шаблоны конструкторов MessageType
S>Это я не понял. Ты что имеешь в виду?
class MyMessageType
{
public:
template<class To, class Msg, class ReplyTo>
MyMessageType(To const&, Msg const&, ReplyTo const&);
};
Такое может быть нужно, чтоб "сфотографировать" полный тип аргументов при инициализации, как, например, это делает boost::shared_ptr.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен