C++ Builder
| Главная | Уроки | Статьи | FAQ | Форум | Downloads | Литература | Ссылки | RXLib | Диски |

 
Шаблоны классов и наследование
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())
и прочие прийдется самому делать ,а я даже
не пойму как это реализуются sad.gif

Asher
Отправлено: 16.06.2003, 20:56


Мастер участка

Группа: Модератор
Сообщений: 550



У меня конечно есть версия, что ClassName() и
ClassNameIs("") сделаны совсем по простому, но тогда получается, что я должен сам имя класса вручную в какую-то строковую константу вписывать.
В принципе и с остальными можно также разобраться и есть подозрение что на самом деле это за меня компилятор делает, неявно biggrin.gif

Но если отказаться от VCL, то непонятно как указатель на себя в стандартных событиях (т.е. TObject *Sender) передавать sad.gif
Георгий
Отправлено: 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().

обратите внимание — шаблоны здесь ни причём.

проект с выше написанным кодом прилагается — может кому пригодится

User Attached Image Скачать файл
rtti.rar


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 конкретного объекта и снаружи все эти трудности не видно, НО внутри объекта хотелось бы обрабатывать накапливаемые данные в родном формате. sad.gif
Плюс над всем этим есть центральный вычислитель, для которого все объекты являются переменными и он смотрит напрямую в Data ohmy.gif . Это конечно немного криво, но пока ничего другого придумать не удалось. sad.gif
Вот и захотел MTAClient сделать шаблоном, тип данных указывать на этапе создания, а дальше порождаться от него как ни в чем не бывало biggrin.gif

P.S. Вы когда нибудь спите? а то часто замечаю что пишете по ночам biggrin.gif

Отредактировано 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



Да как тебе сказать...
Не то чтобы совсем так, как хотелось. Пока сделал так, а когда появится время или мысли по-умней, то переделаю wink.gif
Завел базовый класс данных, а от него породил шаблон. Большинство методов для обработки данных в базовом абстрактые, с реализацией в шаблоне.
В базовом дополнительно сделаны прототипы записи/чтения данных с перегрузкой функций. Как следствие свойство данных в базовом отсутствует, весь доступ приходится делать через вызов функций.
Для записи это выглядит прилично :
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. Перегрузка работает только для функций с разным числом или типом передаваемы параметров, по разному типу возвращаемого значения работать не хочет sad.gif
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`у? А то я уже выдохся — сил нет давать нормальные (полные и корректные) ответы.

Вернуться в Вопросы программирования в C++Builder