У меня БОЛЬШАЯ проблема. Надо позволить пользователю описывать тип объекта, который затем будет создан (задание произвольного атрибута для продукции в атрибутных граммтиках). Но! Как создать объект, елси его тип при компиляции неизвестен.
Здравствуйте LenaLesh, Вы писали:
LL>Пивет всем!
LL>У меня БОЛЬШАЯ проблема. Надо позволить пользователю описывать тип объекта, который затем будет создан (задание произвольного атрибута для продукции в атрибутных граммтиках). Но! Как создать объект, елси его тип при компиляции неизвестен.
LL>Заранее спасибо. Лена
Здравствуйте LenaLesh, Вы писали:
LL>Пивет всем!
LL>Как создать объект, елси его тип при компиляции неизвестен.
Интересно это как? В моем скромном понимании, на момент компиляции все конкретные типы должны быть известны (объявлены, хотя бы). Тут похоже случай "программирования метатипов".
А вообще интересно, по ходу. Компиляторы С++ уже давно как умеют генерировать RTTI. Однако, почему его возможности так ограничены? Иногда было бы удобно, иметь переменную, представляющую тип. Достадочно иметь такое определения для классов, как это реализованно в Delphi. Такие случаи как представлены наверху, решались бы довольно просто — можно было бы менять значение такой переменной во время исполнения и на основании ее конструировать нужный тип. А то придумывают фабрики-классы, производящии функции, клонирующуюся объекты, всякие вариации виртуальных конструкторов... Или это специально так задуманно, чтобы не было желания злоупотребить RTTI?
Здравствуйте Fantasist, Вы писали:
F>Здравствуйте LenaLesh, Вы писали:
LL>>Пивет всем!
LL>>Как создать объект, елси его тип при компиляции неизвестен.
F> F> Интересно это как? В моем скромном понимании, на момент компиляции все конкретные типы должны быть известны (объявлены, хотя бы). Тут похоже случай "программирования метатипов".
F> А вообще интересно, по ходу. Компиляторы С++ уже давно как умеют генерировать RTTI. Однако, почему его возможности так ограничены? Иногда было бы удобно, иметь переменную, представляющую тип. Достадочно иметь такое определения для классов, как это реализованно в Delphi. Такие случаи как представлены наверху, решались бы довольно просто — можно было бы менять значение такой переменной во время исполнения и на основании ее конструировать нужный тип. А то придумывают фабрики-классы, производящии функции, клонирующуюся объекты, всякие вариации виртуальных конструкторов... Или это специально так задуманно, чтобы не было желания злоупотребить RTTI?
Потому что ключевое слово в RTTI — это I (information).
Вот если бы оно называлось RTTM (run-time type management) или еще как-то в этом роде (reflection etc.) — тогда было бы другое дело
J>Потому что ключевое слово в RTTI — это I (information). J>Вот если бы оно называлось RTTM (run-time type management) или еще как-то в этом роде (reflection etc.) — тогда было бы другое дело
Так нам только информация и нужна. Все что нужно — это знать размер класса и адрес его конструктора. Не так уж и много требуется — дальше на помощь приходит полиморфизм.
Здравствуйте Fantasist, Вы писали:
F> А вообще интересно, по ходу. Компиляторы С++ уже давно как умеют генерировать RTTI. Однако, почему его возможности так ограничены?
Потому что это С++ и RTTI при строгой типизации, коей славится С++, нафиг не нужна. Кстати, для сабжа вообще лучше взять более другой язык.
F>Иногда было бы удобно, иметь переменную, представляющую тип. Достадочно иметь такое определения для классов, как это реализованно в Delphi.
В дельфях это реализовано достаточно криво, во всяком случае при отображении на С++Билдер. Есть много ограничений и нестандартностей. Для сабжа оказывается все равно лучше использовать встроенные средства С++ (фабрики классов и т.д.).
F>Такие случаи как представлены наверху, решались бы довольно просто — можно было бы менять значение такой переменной во время исполнения и на основании ее конструировать нужный тип. А то придумывают фабрики-классы, производящии функции, клонирующуюся объекты, всякие вариации виртуальных конструкторов... Или это специально так задуманно, чтобы не было желания злоупотребить RTTI?
Все эти самопальные "придумки" замечательно упаковываются в какую-то "библиотеку" и затем используются, ничего такого страшного, особенно если понимаешь как оно работает Зато решение получается гибче и масштабируемее любого RTTI.
P.S. Пара вопросов для размышления:
1) Какой конструктор вызывать при конструировании "по типу" и какие параметры ему
передавать?
2) Сколько будет "мусора" в exe при достаточно продвинутой RTTI (названия классов, методов, описание их типов и т.п.), если учесть, что при использовании темплейтов "на лету" могут генерится тысячи типов.
V>Все эти самопальные "придумки" замечательно упаковываются в какую-то "библиотеку" и затем используются, ничего такого страшного, особенно если понимаешь как оно работает Зато решение получается гибче и масштабируемее любого RTTI.
Вот тебе и библиотека — тут тебе и фабрики и прочий мусор
Здравствуйте Vladik, Вы писали:
V>В дельфях это реализовано достаточно криво, во всяком случае при отображении на С++Билдер.
Ну, Билдер в моем понимании является странным артефактом совмещения С++ и Delphi.
Так что не будем о нем, если там так же как в Delphi, то там все завязанно на TObject, а это совсем не то.
V>Все эти самопальные "придумки" замечательно упаковываются в какую-то "библиотеку" и затем используются, ничего такого страшного, особенно если понимаешь как оно работает
Абсолютно ничего страшного. Я же не о страхе говорю, а о наглядности и удобности.
V>Зато решение получается гибче и масштабируемее любого RTTI.
Верно для сложных конструкций. Для простых это получается слегка overcoding.
Вот пример из практики: есть сервер, выполняющий небольшую роль: он ждет пока подключиться клиент и создает для него сессию, которая дальше с этим клиентом и общается. Положим, что сессия представлена неким классом Session. Теперь мы хотим сделать такой класс абстрактным, чтобы можно было в его наследниках переопределять разную логику обслуживания клиента, через общий интерфейс. После этого, серверу нужно создавть правильный класс сессии. Какие здесь могуть быть решения? Что первое приходит в голову из стандартных: сделать в сервере какой-нибудь виртуальный ClientConnect, который клиентский код должен перегрузить, чтобы там создавать нужный Session. Так для каждого нового типа Session придется перегружать и тип сервера. Нехорошее решение. Еще из стандартных: делаем
class BasicSession
{
.....
public:
virtual BasicSession* CreateNew()=0;
....
}
Далее перегружаем для каждого класса сессии метод CreateNew, а в сервере заводим переменную типа BasicSession*, и в ран-тайме мы создаем на этой переменной новый объект нужного нам типа сессии. Далее сервер использует правильный перегруженный метод CreateNew, для создания нужного объекта. То есть мы используем экземпляр класса, как информацию о типе этого класса. Однако опять довольно много лишних манипуляций, плюс хранение в памяти объекта который нам не нужен. Можно узакатель на объект в сервере заменить указателем на производящую функцию, и в ран-тайме присваивать указателю значение правильной производящей функции, которая конструирует нужный объект. Теперь хранить экземпляр класса не нужно, но список производящих функций выглядит в коде неуклюже.
Как эта феня решается в Delphi:
type
BasicSession=class
...
end;
TSessionType=class of BasicSession;
TServer=class
SessionType:TSessionType;
....
end;
Не правда ли смотрится коротко и ясно. Теперь в run-time переменной SessionType можно присвоить значение любого типа унаследованного от BasicSession, и сервер в run-time будет создавать правильные объекты.
V>P.S. Пара вопросов для размышления: V>1) Какой конструктор вызывать при конструировании "по типу" и какие параметры ему V>передавать?
Ну придется ограничется конструктором по умолчанию.
Хотя если подумать....
V>2) Сколько будет "мусора" в exe при достаточно продвинутой RTTI (названия классов, методов, описание их типов и т.п.), если учесть, что при использовании темплейтов "на лету" могут генерится тысячи типов.
Во-первых, продвинутой RTTI не нужно. Как я сказал, меня бы устроил такой минимум.
Во-вторых, если RTTI все-равно генерируется, то уже одно имя типа может занять очень приличное количество памяти("class Session" — 13 байт). Я бы вместо имени типа предпочел
те 8 байт о которых упоминал.
Здравствуйте Fantasist, Вы писали:
F>Здравствуйте jazzer, Вы писали:
F>Так нам только информация и нужна. Все что нужно — это знать размер класса и адрес его конструктора.
Здравствуйте Fantasist, Вы писали:
F> Верно для сложных конструкций. Для простых это получается слегка overcoding.
Ой... да ладно вам. "Шаблон" (модное в последнее время название) известен, кодится с нуля на голом С++ за 15 минут. Кстати, мне еще на практике ни разу подобные задачи не встречались. Либо они уже решены кем-то (в тех же ATL и MFC), либо хватает виртуальных функций, либо можно легко найти готовое решение типа приводившегося здесь уже. В чисто академических целях я такое пробовал делать (в каком-то споре с любителем дельфового TObject), никаких проблем не возникало.
F> Не правда ли смотрится коротко и ясно. Теперь в run-time переменной SessionType можно присвоить значение любого типа унаследованного от BasicSession, и сервер в run-time будет создавать правильные объекты.
Чего-то я все равно не догоняю. Кто мешает вместо TSessionType, завести указатель на функцию-создатель объекта? Что-то типа:
template <typename T>
class TSessionType
{
public:
static T *Create(){return new T;}
};
class TMySession : public TSessionType<TMySession>
{
};
В рантайме:
server.SessionType=&TMySession::Create;
F> V>>P.S. Пара вопросов для размышления: V>>1) Какой конструктор вызывать при конструировании "по типу" и какие параметры ему V>>передавать? F> Ну придется ограничется конструктором по умолчанию. F> Хотя если подумать....
Если подумать, то конструктора по умолчанию может и не быть. И в билдере, например, тут начинаются большие проблемы. Мало того, проблемы там начинаются даже если ты просто _явно_ не перегрузил конструктор базового типа (пусть даже и без параметров). Или даже если ты просто забыл слово virtual поставить. В случае "стандартного" плюсового решения такие глюки не прокатывают.
V>>2) Сколько будет "мусора" в exe при достаточно продвинутой RTTI (названия классов, методов, описание их типов и т.п.), если учесть, что при использовании темплейтов "на лету" могут генерится тысячи типов. F>Во-первых, продвинутой RTTI не нужно. Как я сказал, меня бы устроил такой минимум. F>Во-вторых, если RTTI все-равно генерируется, то уже одно имя типа может занять очень приличное количество памяти("class Session" — 13 байт).
Это смотря какая RTTI. За VC я такого, например, не замечал
F>Я бы вместо имени типа предпочел F>те 8 байт о которых упоминал.
Здравствуйте Klestov, Вы писали:
J>>>Потому что ключевое слово в RTTI — это I (information). J>>>Вот если бы оно называлось RTTM (run-time type management) или еще как-то в этом роде (reflection etc.) — тогда было бы другое дело
K>Да и RTTI — Run Time Type Identification :-))
Здравствуйте Анатолий Широков, Вы писали:
F>>Теперь хранить экземпляр класса не нужно, но список производящих функций выглядит в коде неуклюже
АШ>Можно же это дело обернуть как следует:
Предложенный вами метод применялся и в МФС и во многих других библиотеках, регистрация на вызове конструктора статик поля, и если я помню, поднимались на форуме проблемы с некоректной инициализацией статик полей, кажется кто-то упоминал что компилятор не гарантирует конструирования статик поля до вызова кода, который не обращается напрямую к этому полю, другими словами, класс еще не пропишется в карточке, а в коде будет уже обращение к карточке, где будет искаться функция-создатель по имени класса.
Также, при использовании такого метода регистрации совместно с компилятором Code Warior, возник совсем неожиданный трабл, компилятор категорически не хотел включать в исполняемый модуль объектник того класса, экземпляры которого явным образом не создаются в других объектниках, ведь функция создатель находится рядом с классом, в том же объектнике. Линкер не обнаруживал внешних ссылок на объектник, и тихонько его выкусывал. Может это и конфигурируется, незнаю. Как быть в таком случае. Так же я слышал что рассматривается предложение по приминению динамической загрузки С++ классов на момент необходимости ( понятия не имею как, кто бы просветил ), может вышеупомянутый трабл имеет непосредственное отношение к данному фичеру?
Will I live tomorrow? Well I just can't say
But I know for sure — I don't live today.
Jimi Hendrix.
Здравствуйте Vladik, Вы писали:
V>Здравствуйте Fantasist, Вы писали:
V>Ой... да ладно вам. "Шаблон" (модное в последнее время название) известен, кодится с нуля на голом С++ за 15 минут. Кстати, мне еще на практике ни разу подобные задачи не встречались. Либо они уже решены кем-то (в тех же ATL и MFC), либо хватает виртуальных функций, либо можно легко найти готовое решение типа приводившегося здесь уже.
Во-первых, скорость кодирования никак не уменьшает объема кода. Решение, конечно, можно найти всегда (ну почти всегда). За что я люблю С++ так как раз за то, что почти на любую проблему можно изобрести весьма неплохое решение, а то и очень красивое решения — в более простых языках синтаксис не позволяет. Но я говорю как раз о наглядности и простоте. Объявление одного типа и одной переменной, на мой взгляд, выглядят понятнее.
V>Чего-то я все равно не догоняю. Кто мешает вместо TSessionType, завести указатель на функцию-создатель объекта? Что-то типа:
V>template <typename T> V>class TSessionType V>{ V>public: V> static T *Create(){return new T;} V>};
V>class TMySession : public TSessionType<TMySession> V>{ V>};
Много чего. Это один из вариантов который я упомянул, с интересной модификацией. Но давайте рассмотрим:
template <typename T>
class TSessionType
{
public:
static T *Create(){return new T;}
};
class BasicSession: public TSessionType<TBasicSession> //базовый класс
{
public:
virtual int DoIt(); //если захочеться сделать абстрактным, то облом. Но на это можно не обращать внимания.
};
//приходиться наследовать каждый тип с правильным параметром для TSessionTypeclass TMySession:public TBasicSession, public TSessionType<TMySession>
{
public:
int DoIt()
{ };
};
typedef TBasicSession* (*TSessCreator)();
class TServer
{
public:
TSessCreator cr;
};
....
TServer srv;
srv.cr=&TMySession::Create; //какой? TMySession::Create() или TBasicSession::Create()? //И скорее всего несовпадение типов. Тип функции это не тоже самое, что тип возвращаемого значения.
Плюс к тому и не так наглядно. По мне так лучше уж хранить экземпляр как информатор, но это тоже не так здорово.
Но если подумать, то можно пожалуй, что-то и полудше придумать.
V>Если подумать, то конструктора по умолчанию может и не быть.
Это понятно, но я говорил о том, что можно наложить ограничения на использования переменной типа только для типов имеющий конструктор по умолчанию.
V>Это смотря какая RTTI. За VC я такого, например, не замечал
Да правда, тут я поторопился.
V>8 байт не спасут отца русской демократии
Здравствуйте Анатолий Широков, Вы писали:
F>>Теперь хранить экземпляр класса не нужно, но список производящих функций выглядит в коде неуклюже
АШ>Можно же это дело обернуть как следует:
Можно. Обернуть можно и лучше, для конкретного случая, но это по большому счету то же самое.
F>Во-вторых, если RTTI все-равно генерируется
АШ>Это если очень попросишь компилятор
А тут это моя обрубленная фраза не полноценна. Я имел в виду, что "если генерируется, то можно добавить и это" а не "так как оно в любом случае генерируется, то терять нам нечего"
Здравствуйте, Fantasist, Вы писали:
F>Во-первых, скорость кодирования никак не уменьшает объема кода.
Если нужны готовые решения на все случаи жизни — я возьму джаву
F>Решение, конечно, можно найти всегда (ну почти всегда). За что я люблю С++ так как раз за то, что почти на любую проблему можно изобрести весьма неплохое решение, а то и очень красивое решения — в более простых языках синтаксис не позволяет. Но я говорю как раз о наглядности и простоте. Объявление одного типа и одной переменной, на мой взгляд, выглядят понятнее.
Выглядит может и понятнее, но работает кривее
V>>Чего-то я все равно не догоняю. Кто мешает вместо TSessionType, завести указатель на функцию-создатель объекта? Что-то типа: V>>template <typename T> V>>class TSessionType V>>{ V>>public: V>> static T *Create(){return new T;} V>>};
V>>class TMySession : public TSessionType<TMySession> V>>{ V>>};
F>Много чего. Это один из вариантов который я упомянул, с интересной модификацией. Но давайте рассмотрим:
Я тут подумал, есть еще более интересная и простая модификация
F>template <typename T>
F>class TSessionType
F>{
F>public:
F> static T *Create(){return new T;}
F>};
F>class BasicSession: public TSessionType<TBasicSession> //базовый класс
F>{
F>public:
F> virtual int DoIt(); //если захочеться сделать абстрактным, то облом. Но на это можно не обращать внимания.
F>};
Здесь можно было сделать TBasicSession также темплейтным, и никаких множественных наследований ;)
[...]
F>//И скорее всего несовпадение типов. Тип функции это не тоже самое, что тип возвращаемого значения.
F>
С приведением типов тоже можно все обернуть так, что компилятор ругаться не будет.
[...] V>>Если подумать, то конструктора по умолчанию может и не быть. F> Это понятно, но я говорил о том, что можно наложить ограничения на использования переменной типа только для типов имеющий конструктор по умолчанию.
Как ты себе это представляешь? У базового типа конструктор по умолчанию есть. У наследника нет. Что будет вызываться при конструировании наследника через RTTI базового типа? Кривизна только будет, не надо оно.
Ничего подобного. В С++ оно просто не работает, а значит и говорить о ее кривости нечего.
V>Я тут подумал, есть еще более интересная и простая модификация
V>
Ничего подобного! Я это первый придумал! Честно. Когда писал Вам ответ, хотел привести хорошую алтернативу, и придумал такую вещь. Тока у меня VC ее не скомпилил с забавной ошибкой: class TBasicSession *__cdecl SessCreator(void)' : expects at least 0 argument — 1 provided А пока я не выяснил насколько он прав, постить не захотел.
V>Как ты себе это представляешь? У базового типа конструктор по умолчанию есть. У наследника нет. Что будет вызываться при конструировании наследника через RTTI базового типа? Кривизна только будет, не надо оно.
Ну все убедил. Вот ведь придумали нехорошие люди, чтобы можно было не делать конструкторов по умолчанию. На практике же, почти всегда приходится его определять. Нет чтобы сделать конструктор обыкновенной виртуальной функцией. Ну не прямо так, виртуальной функцией, но концептуально как-нибудь так.
Все, больше не буду позориться разговорами о продвинутой RTTI в С++. Везет, что пока еще нулей не нахватал, так что не будем дальше испытывать судьбу. Тем более, что поднял я этот вопрос чисто чтобы "разговор поддержать", а не то, что мне этого действительно не хватает.
Здравствуйте, Fantasist, Вы писали:
V>>Выглядит может и понятнее, но работает кривее F> Ничего подобного. В С++ оно просто не работает, а значит и говорить о ее кривости нечего.
В дельфях получишь ту же кривизну, если забудешь определить нужный конструктор. Но в сях кривизны будет больше, т.к. там конструкторы еще и инитят вложенные объекты, так что конструктор у объекта должен быть сгенерирован всегда, даже если он "как-бы" ничего и не делает. Билдер, естественно, подобные проблемы не волнуют — так что нужно самому не зевать и не забывать у тех же автокриэйтных форм явно прописывать конструктор, даже если такой конструктор "как-бы" ничего и не делает.
[...]
F>Ничего подобного! Я это первый придумал! Честно. Когда писал Вам ответ, хотел привести хорошую алтернативу, и придумал такую вещь. Тока у меня VC ее не скомпилил с забавной ошибкой: class TBasicSession *__cdecl SessCreator(void)' : expects at least 0 argument — 1 provided А пока я не выяснил насколько он прав, постить не захотел.
У VC я тоже сталкивался с проблемой взятия адреса темплейтных функуий. Не помню как обходил.
[...] F> Ну все убедил. Вот ведь придумали нехорошие люди, чтобы можно было не делать конструкторов по умолчанию. На практике же, почти всегда приходится его определять. Нет чтобы сделать конструктор обыкновенной виртуальной функцией. Ну не прямо так, виртуальной функцией, но концептуально как-нибудь так.
В виртуальности конструктора (которую можно наблюдать в билдере) наряду с достоинствами есть и "концептуальные" недостатки. Например вызов из базовых конструкторов перегруженных методов наследника в то время как наследник еще не сконструирован. То же самое отностится и к деструкторам. Кроме того, из соображений эффективности, приделывать к каждому классу таблицу вертуальных функций не стоит. Оставлять существующие невиртуальные конструкторы совместно с виртуальными — тоже криво.