Asher |
Отправлено: 16.06.2003, 16:26 |
|
Мастер участка
Группа: Модератор
Сообщений: 550
|
Можно ли создать шаблон — потомок класса (в частности абстракного) и потом далее от него наследоваться?
P.S. Надоело делать свойство неизвестного типа void, а потом каждый раз приводить
|
|
Георгий |
Отправлено: 16.06.2003, 19:14 |
|
Почетный железнодорожник
Группа: Модератор
Сообщений: 874
|
вот такое нужно?
CODE |
template <class T>
class base{
public:
virtual T& get(void)=0;
virtual void set(T& t)=0;
};
template <class T1>
class a:public base<T1>
{
private:
T1 TField1;
public:
T1& get(void){return TField1;};
void set(T1& T){TField1=T;};
};
| |
|
Asher |
Отправлено: 16.06.2003, 20:36 |
|
Мастер участка
Группа: Модератор
Сообщений: 550
|
Да. Про наследовании от шаблона в STL есть, вроде все понятно, но
основной вопрос все-таки — можно ли шаблон породить от чего-либо вроде TObject, а то вещи вроде
ClassName()
ClassNameIs("")
InheritsFrom(__classid())
и прочие прийдется самому делать ,а я даже
не пойму как это реализуются
|
|
Asher |
Отправлено: 16.06.2003, 20:56 |
|
Мастер участка
Группа: Модератор
Сообщений: 550
|
У меня конечно есть версия, что ClassName() и
ClassNameIs("") сделаны совсем по простому, но тогда получается, что я должен сам имя класса вручную в какую-то строковую константу вписывать.
В принципе и с остальными можно также разобраться и есть подозрение что на самом деле это за меня компилятор делает, неявно
Но если отказаться от VCL, то непонятно как указатель на себя в стандартных событиях (т.е. TObject *Sender) передавать
|
|
Георгий |
Отправлено: 16.06.2003, 22:04 |
|
Почетный железнодорожник
Группа: Модератор
Сообщений: 874
|
шаблон (template) — это псевдо-обьект, который существует только во время компиляции программы и является эквивалентом макросов в чистом Си (тут надобы сделать ряд уточнений, но в данном случае это не принципиально). Именно поэтому у шаблонов нет и быть не может ни предков, ни потомков. Во время исполнения программы (runtime) понятия шаблон уже нет, а есть экземпляры функций и методов классов (автоматически генерируемых компилятором), по одной для работы с каждым типом шаблонов.
т.е.
azi;
azf;
экземпляры класса a — zi и zf на самом деле являются совершенно разными классами, но только заботу об их определении берёт на себя компилятор.
тебе наверное нужно банальное наследование? тогда:
CODE | class aabbcc:public TObject
{
}; |
и вызов:
CODE | aabbcc* z=new aabbcc;
MessageBox(0,AnsiString(z->ClassName()).c_str(),"ClassName",MB_OK);
MessageBox(0,AnsiString(typeid( *z).name()).c_str(),"typeid",MB_OK);
delete z; |
как ты видишь ничего не переопределяется — всё уже занесено в механизм RTTI на этапе трансляции программы.
Только отключить это не удалось — что означает project options/c++/exception handling/enable RTTI?
если наследование не нужно или не совсем нужно, то на пальцах покажи, что надо.
|
|
Георгий |
Отправлено: 17.06.2003, 01:04 |
|
Почетный железнодорожник
Группа: Модератор
Сообщений: 874
|
о я, кажется, понял, что надо.
постановка задачи:
1.есть указатель на базовый класс (т.е. на экземпляр обьекта наследника этого базового класса)
2.есть желание узнать какой же обьект скрывается за этим указателем
2а — нужно проверить может ли этот обьект быть представлен как обьект А
2б — нужно узнать тип обьекта
как бы решение (с использованием RTTI):
CODE | class a
{
public:
virtual AnsiString WhoIsIt(void)const=0;
};
class b:public a
{
public:
AnsiString WhoIsIt(void)const{return "instance of B";};
};
class c:public b
{
public:
AnsiString WhoIsIt(void)const{return "instance of C";};
};
class d:public a
{
public:
AnsiString WhoIsIt(void)const{return "instance of D";};
};
void __fastcall TForm1::Button1Click(TObject *Sender)
{
a *ptrA,*arrPtrA[3];
b insB,*ptrB;
c insC,*ptrC;
d insD,*ptrD;
int i;
arrPtrA[0]=&insB;
arrPtrA[1]=&insC;
arrPtrA[2]=&insD;
AnsiString str;
for (i=0;i<3;i++)
{
ptrA=arrPtrA[i];
str.printf("step # %i",i);
this->Memo1->Lines->Add(str);
str.printf("typeinfo.name=%s",typeid(*ptrA).name());
this->Memo1->Lines->Add(str);
str.printf("ptrA->WhoIsIt=%s",ptrA->WhoIsIt());
this->Memo1->Lines->Add(str);
ptrB=dynamic_cast<b*>(ptrA);
str.printf("ptrB=%p",ptrB);
this->Memo1->Lines->Add(str);
ptrC=dynamic_cast<c*>(ptrA);
str.printf("ptrC=%p",ptrC);
this->Memo1->Lines->Add(str);
ptrD=dynamic_cast<d*>(ptrA);
str.printf("ptrD=%p",ptrD);
this->Memo1->Lines->Add(str);
};
} |
форсировать использование RTTI для обьекта можно указав __rtti:
[/CODE]class __rtti Alpha{...};[/CODE]
если надо сделать нечто подобное на ANSI C++, то придётся описывать базовый класс — что то типа TObject и через него создавать подобие RTTI — т.е. всё другие обьекты должны быть потомками TObject и перегружать методы аналогичные WhoIsIt().
обратите внимание — шаблоны здесь ни причём.
проект с выше написанным кодом прилагается — может кому пригодится
|
|
Asher |
Отправлено: 17.06.2003, 09:43 |
|
Мастер участка
Группа: Модератор
Сообщений: 550
|
Уважаемый Георгий. Спасибо за деятельное внимание к моей проблеме. Но все-таки попробую объяснить более подробно. Есть программа, состоящая из большого числа ~300 взаимодействующих независимых объектов (~20 видов), порожденных от одного базового абстрактного класса (MTAObject : public TObject) и взаимодействующих посредством событий.
Все свои классы ничинаю с М, если порождаюсь от VCL то добавляю Т, а для абстрактных классов — добавляю А. Это позволяет достаточно удобно и прозрачно рисовать иерархию классов, например в Visio.
Через свойства и методы базового объекта происходит сохранение/восстановление настроек объекта, унифицированный вызов показа свойств в панели по тиge ObjectInspector'а, выдача внутреннего состояния и расшифровка кодов ошибок работы.
Есть 'главный' объект, который ведает созданием (с присвоением сквозного индекса и сохранением их в list) и удалением остальных объектов, и отображением их всех в виде дерева, для доступа к их свойствам
Следующий класс — MTAClient : public MTAObject имеет в своем составе объект посредник и методы, для навешивания на события посредника, как то приход данных (unsigned char *value), изменение достоверности источника данных (bool), измеенение источника(TObject *Sender)
Сам MTAClient также содержит признак достоверности своих данных (Valid)и массив данных(Data). Вот эти данные могут быть различных типов, в общем случае сведенные к float и AnsiString.
далее существует MTAServer : public MTAClient умеющий принимать указатели на посредников, сохранять их в внутренем списке и дергать их события в случае изменения признака достоверности данных и изменения данных (переопределены от MTAClient )
Вообще далее от MTAClient и MTAServer порождаются все остальные классы, для выполенения своих специфических функций и получается что или void *Data, или две параллельные ветви.
Все взаимодействие порожденных классов строится на вызове событий посредников (они двунаправленные) данные мередаются через массив unsigned char *value, заполняемый через union конкретного объекта и снаружи все эти трудности не видно, НО внутри объекта хотелось бы обрабатывать накапливаемые данные в родном формате.
Плюс над всем этим есть центральный вычислитель, для которого все объекты являются переменными и он смотрит напрямую в Data . Это конечно немного криво, но пока ничего другого придумать не удалось.
Вот и захотел MTAClient сделать шаблоном, тип данных указывать на этапе создания, а дальше порождаться от него как ни в чем не бывало
P.S. Вы когда нибудь спите? а то часто замечаю что пишете по ночам
Отредактировано Asher — 17 Jun 2003, 10:44
|
|
Георгий |
Отправлено: 17.06.2003, 13:17 |
|
Почетный железнодорожник
Группа: Модератор
Сообщений: 874
|
для конкретики я попробовал представить основную идею того, что, по моему мнению у тебя есть, в виде кода
т.е. есть что-то вроде такого набора классов:
CODE | class AClient
{
public:
virtual bool WasChanged(void)=0;
virtual void ProcessAnotherChange(void*)=0;
virtual const void* GetTransmitData(void)const=0;
virtual ~AClient(void)=0;
};
class AHead:public AClient
{
public:
virtual void AddHandler(AClient*)=0;
virtual void Process(void)=0;
virtual ~AHead(void)=0;
}; |
и экземпляр AClient в методе ProcessAnotherChange может получить только void* на (изменившиеся) данные?
и обрабатывает их примерно так:
CODE | ProcessAnotherChange(void* td)
{
MyData*ptrMyData;
ptrMyData=(MyData*)td;
if (ptrMyData->Tag==MyTag)//проверка типа структуры — наша не наша или это не так делаешь?
{
//выполняется обработка
};
}; |
если да — то почему не ввести ещё один класс ATransmitData? тогда прототипы будут выглядеть так:
CODE | class ATransmitData
{
public:
virtual void* GetDataBuffer(void)=0;
virtual DWORD GetDataBufferSize(void)=0;
virtual ~ATransmitData(void)=0;
};
class AClient
{
public:
virtual bool WasChanged(void)=0;
virtual void ProcessAnotherChange(ATransmitData*)=0;
virtual const ATransmitData* GetTransmitData(void)const=0;
virtual ~AClient(void)=0;
};
class AHead:public AClient
{
public:
virtual void AddHandler(AClient*)=0;
virtual void Process(void)=0;
virtual ~AHead(void)=0;
}; |
потомки от ATransmitData могут быть сделаны по шаблону:
CODE | template <class T>
class MTransmitData:public ATransmitData
{
public:
T localBuffer;
virtual void* GetDataBuffer(void){return &localBuffer;};
virtual DWORD GetDataBufferSize(void){return sizeof(localBuffer);};
virtual ~MTransmitData(void){return;};
};
MTransmitData<int> a;
MTransmitData<AnsiString> b;
|
а обработчик ProcessAnotherChange сможет преобразовать в настоящий потомок примерно так:
CODE | ProcessAnotherChange(ATransmitData* td)
{
MyData*ptrMyData;
ptrMyData=dynamic_cast<MyData*>(td);
if (ptrMyData)
{
//выполняется обработка
};
}; |
это оно?
если да, то придётся переделать кучу кода, что скорее всего приведёт к опечаткам — не завидую я тебе.
сова я, да и без работы сижу — вот по ночам и пишу. Стараюсь не потерять навыки, а то приспичит что-то написать, а уже забыл как...
Отредактировано Георгий — 18 Jun 2003, 11:02 |
|
Asher |
Отправлено: 18.06.2003, 12:42 |
|
Мастер участка
Группа: Модератор
Сообщений: 550
|
Спасибо, надо все хорошо обдумать и попробовать. Сейчас я немного занят (23 жестокий DeadLine), поэтому быстро ответить не смогу.
|
|
Георгий |
Отправлено: 26.08.2003, 20:53 |
|
Почетный железнодорожник
Группа: Модератор
Сообщений: 874
|
Asher
удалось что-нибудь сделать? |
|
Asher |
Отправлено: 27.08.2003, 08:58 |
|
Мастер участка
Группа: Модератор
Сообщений: 550
|
Да как тебе сказать...
Не то чтобы совсем так, как хотелось. Пока сделал так, а когда появится время или мысли по-умней, то переделаю
Завел базовый класс данных, а от него породил шаблон. Большинство методов для обработки данных в базовом абстрактые, с реализацией в шаблоне.
В базовом дополнительно сделаны прототипы записи/чтения данных с перегрузкой функций. Как следствие свойство данных в базовом отсутствует, весь доступ приходится делать через вызов функций.
Для записи это выглядит прилично :
CODE | virtual void __fastcall SetValue(double value){};//
virtual void __fastcall SetValue(AnsiString value){};// |
а для чтения похуже:
CODE | virtual bool __fastcall GetValue(double &value){return false;};//
virtual bool __fastcall GetValue(AnsiString &value){return false;};// |
Это дает отсутствие неразришимых ссылок и корректный возврат результата в случае неправильной подстановки перекрытия
Шаблон их перекрывает своими:
CODE | virtual void __fastcall SetValue(T value);//
virtual bool __fastcall GetValue(T &value);/ |
Как следствие действия возможны только в методах совпадающих по инициализации шаблона.
Внутри шаблона STL vector, для хранения данных
Шаблон создается и удаляется статическими методами базового класса, а его собственные конструктор и деструктор в Protected
Большой минус — для кждого нового типа данных приходится делать секции создания и удаления в базовом классе, плюс описывать дополнительные перегруженные функции доступа к данным.
P.S. Перегрузка работает только для функций с разным числом или типом передаваемы параметров, по разному типу возвращаемого значения работать не хочет
P.P.S. К сожалению некогда занятся этим по-плотному. Начальство требует результат, а в отпуск в этом году я еще не ходил (собирался дома спокойно поэксперементировать) — работы много.
P.P.P.S. что-то я так и немогу сделать вставку файла в ответ — если будет интересовать могу выслать мылом.
|
|
Георгий |
Отправлено: 27.08.2003, 20:18 |
|
Почетный железнодорожник
Группа: Модератор
Сообщений: 874
|
Это у тебя была, судя по всему, типичная коммуникационная задача. Есть транспортная среда, которая только поток байт умеет передавать и уровень представления данных, которому на поток байт начихать — он хочет более высоких материй. Я в очередной раз разрабатываю средства коммуникации (сначала был обмен с железкой по rs232, потом modbus, а теперь Qnet-fleet) и каждый раз возникает задача пропихнуть через тупую сеть объект. Т.е. объект нужно преобразовать в детерминированный блок памяти (массив с изветсной длиной) а потом из этого массива воссоздать объект.
В случае с тупой транспортной средой приходится в заголовки пихать код, который говорит, что это за пакет и как его в объект преобразовать (решение этой задачи напоминает твоё решение).
А если есть более интеллектуальная транспортная среда (у тебя это RTTI+доступ к объекту базоваго типа по указателю), можно обратное преобразование (из указателя в объект) переложить на плечи RTTI, а прямое (из объекта в структуру понятную для транспортной среды) свести к возврату указателя на базовый класс.
Мне кажается, что что то ты не совсем так сделал — вот и приходится тебе каждый раз базовый клас править.
А у меня сложности именно с тем, что практически один и тот же код приходится каждый раз заново набирать... И твоя идея с шаблоном мне показалась (да и сейчас кажется) универсальной,но только для передачи данных по указателю.
Отредактировано Георгий — 27/08/2003, 21:23 |
|
Asher |
Отправлено: 28.08.2003, 10:26 |
|
Мастер участка
Группа: Модератор
Сообщений: 550
|
QUOTE | Мне кажается, что что то ты не совсем так сделал — вот и приходится тебе каждый раз базовый клас править |
А я о чем говорю?QUOTE | Не то чтобы совсем так, как хотелось. Пока сделал так, а когда появится время или мысли по- умней, то переделаю |
А если серьезно, то детальное изучение проги показывает что большинству глассов на данные начхать, некоторым большинству нужны только численные(решил делать только double) и только трем (пока) — или численные, или строковые, в зависимости от задачи.
Есть мысль отказаться от базового и оставить только шаблон. В классах владельцах сразу инициализировать его нужным типом (тогда получается что вообще любым), а для классов с несколькими возможными типами данных сделать фабрику. Единственный минус который пока вижу — для каждого создаваемого объекта свой пункт меню в программе делать(все объекты до единого создаются в Run-Time или по командам пользователя или чтением из ini-файла), но это можно и автоматизировать, чтобы они сами себе пункты меню генерили.
Нет, есть еще пара — невозможность сменить тип данных, т.е. только удаление объекта с последующим пересозданием и второй — вывод настроек пользователю (типа: размер массива, способ накопления, обработка переполнения, etc) придется делать в каждом классе где вводятся данные. Хотя это можно (а наверное и нужно) засунуть в шаблон, а потом просто вызывать.
P.S. Похоже в процессе обсуждения мысль оформилась. Еще раз хорошо подумаю и если не найду больших минусов так и сделаю.
P.P.S. Ты вроде писал что теперь под QNX пишешь — вот только что наткнулся http://qnx.org.ru Там и форум есть.
Отредактировано Asher — 28/08/2003, 15:47
|
|
Георгий |
Отправлено: 28.08.2003, 20:16 |
|
Почетный железнодорожник
Группа: Модератор
Сообщений: 874
|
будем ждать результатов размышлений...
А про форум я знаю, даже там под именем "Gerik" зарегистрирован, но там, как то не уютно... Да и люди там, в основном, на чистом C пишут — не поймут меня. А тут столько народа, которым ещё учиться и учиться — например Borgir ( https://rxlib.ru/forums/index.php?ac...89e7a265569d11c ) пытается контрольную сумму подсчитать и удивляется — где же функция Ord... Я, кажется, начитаю понимать, почему профессора и академики в ВУЗ`ах все равно занятия ведут — чтобы понимать, что то, что тебе очевидно, для других может составить целую проблему...
Кстати не поможешь Borgir`у? А то я уже выдохся — сил нет давать нормальные (полные и корректные) ответы. |
|
|