Похоже, я как-то совсем сильно торможу вечером, но не понимаю как (и можно ли) сделать такое.
Есть базовый класс-шаблон. Что-то типа
template<class _Type> class Base
{
public:
static int id;
_Type val;
_Type getVal();
void setVal(_Type& val);
...
}
В этом базовом классе нельзя определить все функции с _Type. Потому наследуемся от него пару раз примерно так:
class StringItem : public Base<std::string>
{
void setVal(std::string& val);
}
class IntItem : public Base<int>
{
void setVal(int& val);
}
Это, тсскзать, ядро. Дальше, в работе надо иметь возможность создавать что-то типа
class MyBlaBlaBlaStringItem : public StringItem
и
class My666IntItem : public IntItem
которые по большому счету от StringItem и IntItem отличаются только значением некоторых полей, например id.
И, под конец, всё это хотелось бы хранить в каком-то контейнере указателей на базовый тип. Как-то типа:
I>Подскажите, пожалуйста, возможно ли реализовать что-то похожее?
Почему нет — можно. главное — не пытаться потом кастить элементы вектора к иным типам. Во-первых, это "прощай ООП", а во-вторых — огрести от компилера/линкера можно по самое не балуйся.
И, конечно, не забудь виртуальные деструкторы
Здравствуйте, <Аноним>, Вы писали:
А>Здравствуйте, Ilias, Вы писали:
I>>И, под конец, всё это хотелось бы хранить в каком-то контейнере указателей на базовый тип. Как-то типа:
I>>
I>>Подскажите, пожалуйста, возможно ли реализовать что-то похожее?
А>Почему нет — можно. главное — не пытаться потом кастить элементы вектора к иным типам. Во-первых, это "прощай ООП", а во-вторых — огрести от компилера/линкера можно по самое не балуйся. А>И, конечно, не забудь виртуальные деструкторы
Насколько я понял, таки нельзя, т.к. Base — шаблонный тип и не понятно какие типы будут возвращать его наследники.
Здравствуйте, Ilias, Вы писали:
I>Подскажите, пожалуйста, возможно ли реализовать что-то похожее?
1) Нехорошо использовать идентификаторы, начинающиеся с подчерка и большой буквы. Можно и нарваться, например на макросы какеие-нибудь
2) Если прототипы неопределённых функций тебе известны уже в шаблоне, то можно их определить путём частичной/полной специализации методов, например так:
template<typename T> class MyClass {
void Foo( T& ); // при использовании надо определить!
};
template<> void MyClass<int>::Foo( int& i ) { i++; }
template<> void MyClass<double>::Foo( int& i ) { i -= 0.5; }
Но гибче сделать как-то так:
namespace MyClassImpl {
// определи для своего типа
// void foo( T& );!!!
} // namespace MyClassImpltemplate<typename T> class CMyClass {
void foo( T& i ) { MyClassImpl::foo( i ); }
};
namespace MyClassImpl {
void foo( int& i ) { i++; }
void foo( double& f ) { f -= 0.5; }
} // namespace MyClassImpl
3) Наверное тебе нужно какие-то функции сделать виртуальными, а ещё в базу можно внедрить счётчик, чтобы можно было использовать интрузивные умные указатели
4) Вся конструкция какая-то очень сложная, если до утра не отпустит, объясни по-человесески, что тебе на самом деле-то нужно
5) Советую сделать 15-минутный перерыв, например пойти прогуляться или попить чаю
С уважением, и пожелаением идти отдохнуть
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, Ilias, Вы писали:
I>>Подскажите, пожалуйста, возможно ли реализовать что-то похожее? E>1) Нехорошо использовать идентификаторы, начинающиеся с подчерка и большой буквы. Можно и нарваться, например на макросы какеие-нибудь
Ну это я как в стл написал, не особо задумываясь. Понятно что это только пример — поправить не сложно.
E>2) Если прототипы неопределённых функций тебе известны уже в шаблоне, то можно их определить путём частичной/полной специализации методов, например так:
template<typename T> class MyClass {
E> void Foo( T& ); // при использовании надо определить!
E>};
E>template<> void MyClass<int>::Foo( int& i ) { i++; }
E>template<> void MyClass<double>::Foo( int& i ) { i -= 0.5; }
E>
А, т.е. можно определять как-то отдельные функции у шаблонов? Тогда второй уровень (StringItem, IntItem ) вообще не нужны наверное.
E>Но гибче сделать как-то так:
namespace MyClassImpl {
E> // определи для своего типа
E> // void foo( T& );!!!
E>} // namespace MyClassImpl
E>template<typename T> class CMyClass {
E> void foo( T& i ) { MyClassImpl::foo( i ); }
E>};
E>namespace MyClassImpl {
E> void foo( int& i ) { i++; }
E> void foo( double& f ) { f -= 0.5; }
E>} // namespace MyClassImpl
Вот это не совсем понял. Зачем два раза namespace MyClassImpl и почему оно гибче, чем первый вариант?
E>3) Наверное тебе нужно какие-то функции сделать виртуальными, а ещё в базу можно внедрить счётчик, чтобы можно было использовать интрузивные умные указатели
Для чего счетчик и указатели? Где их использовать?
E>4) Вся конструкция какая-то очень сложная, если до утра не отпустит, объясни по-человесески, что тебе на самом деле-то нужно
Попробую еще раз. Есть некий объект, с некими действиями, которыми с ним можно производить.
Объекты эти могут быть нескольких разных типов (строка, число и т.д.)
В одной и той же функции для объекта типа строка и объекта типа число может быть (и будет!) разный код.
Т.е. например в setVal(string& val) надо проверять, чтобы в строке не было знаков '-', а в setVal(int& val) надо проверять, чтобы val нацело делилось на 7.
Дальше, после того как определены типы объектов, надо завести несколько конкретных объектов заданного типа. Например объект типа строка с начальным значением этой строки "blablabla", или объект типа число с начальным значением 16.
Причем желательно чтобы конкретный объект типа число с начальным значением 16 и то же самое с начальным значением 17 были разными типами.
Просто этот код будет очень редко меняться, но очень часто использоваться, потому хочется максимально нагрузить компилятор в плане типизации и проверки наибольшего количества ошибок на этапе сборки.
E>5) Советую сделать 15-минутный перерыв, например пойти прогуляться или попить чаю
E>С уважением, и пожелаением идти отдохнуть
Так всю жизнь проотдыхать можно. Фигарить надо, к сожалению
Здравствуйте, Ilias, Вы писали:
I>Ну это я как в стл написал, не особо задумываясь. Понятно что это только пример — поправить не сложно.
Ну форум разные люди читают...
I>Вот это не совсем понял. Зачем два раза namespace MyClassImpl и почему оно гибче, чем первый вариант?
1) Оно не два раза нужно, а столько сколько понадобится, потому, что мало ли какие ещё специализации где пригодятся... Если, вдруг, кто не в курсе, пространство имён можно пополнять сколько хочешь раз.
2) Так гибче потому, что можно использовать шаблоны функций для разных параметров, например так:
namespace MyClassImpl {
void foo( int i ) { std::cout << "0x" << itohex( i ); }
void foo( double d ) { std::cout << d; }
void foo( const char* s ) { assert( s != 0 ); std::cout << "\"" << s << "\""; }
} // namespace MyClassImpl
И теперь будут работать и MyClass<short> и MyClass<int>, и MyClass<const char*>, и MyClass<CString>...
правда нужна ли тебе такая гибкость --
I>Для чего счетчик и указатели? Где их использовать?
Чтобы было удобно владеть элементами вектора по указетелю.
I>Попробую еще раз. Есть некий объект, с некими действиями, которыми с ним можно производить. I>Объекты эти могут быть нескольких разных типов (строка, число и т.д.) I>В одной и той же функции для объекта типа строка и объекта типа число может быть (и будет!) разный код. I>Т.е. например в setVal(string& val) надо проверять, чтобы в строке не было знаков '-', а в setVal(int& val) надо проверять, чтобы val нацело делилось на 7. I>Дальше, после того как определены типы объектов, надо завести несколько конкретных объектов заданного типа. Например объект типа строка с начальным значением этой строки "blablabla", или объект типа число с начальным значением 16. I>Причем желательно чтобы конкретный объект типа число с начальным значением 16 и то же самое с начальным значением 17 были разными типами.
А как ты планируешь звать SetValue у базы, еслине знаешь тип аргкмента? Как ты себе мыслишь клиентский код?
I>Просто этот код будет очень редко меняться, но очень часто использоваться, потому хочется максимально нагрузить компилятор в плане типизации и проверки наибольшего количества ошибок на этапе сборки.
// этот класс лучше взятьиз библиотеки, но, так как сейчас поздно, напишу какой-нибудь, а библиотеку найдёшь потом сам.template<typename T> class MyPtr {
public:
MyPtr() : ptr( 0 ) {}
MyPtr( T* p ) : ptr( p ) { p->AddRef(); }
~MyPtr() { if( ptr != 0 ) ptr->Release(); }
void operator = ( MyPtr other ) { Swap( other ); }
void Swap( MyPtr& other ) { srd::swap( ptr, other.ptr ); }
operator T* () const { return ptr; }
private:
T* ptr;
};
namespace str { template<typename T> swap( MyPtr<T>& a1, MyPtr<T>& a2 ) { a1.Swap( a2 ); }
template<typename T> T& SafeRef( T* p ) { assert( p != 0 ); return *p; }
// Ну а теперь само изделие...template<typename T> class MyValue;
class MyAnstractValue {
public:
int AddRef() { return ++count; }
int Release() { assert( count > 0 ); if( --count == 0 ) delete this; }
template<typename T> MyValue<T>& GetValue() { return SafeRef( dynamic_cast<MyValue<T>*>( this ) ); }
template<typename T> const MyValue<T>& GetValue() const
{ return SafeRef( dynamic_cast<const MyValue<T>*>( this ) ); }
protected:
virtual ~MyAnstractValue() {}
};
template<typename T> class MyValue : public: MyAbstractValue {
public:
MyValue( const T& t ) { SetValue( t ); }
void SetValue( const T& ); // Должно определить в специализацииconst T& GetValue() const { return value; }
private:
T value;
};
Ну и теперь использование:
std::vector< MyPtr<MyAbstractValue> > values;
values.push_back( new MyValue<std::string>( "мама" ) );
values.push_back( new MyValue<int>( 7 ) );
I>Так всю жизнь проотдыхать можно. Фигарить надо, к сожалению Ну-ну...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
// этот класс лучше взятьиз библиотеки, но, так как сейчас поздно, напишу какой-нибудь, а библиотеку найдёшь потом сам.template<typename T> class MyPtr {
public:
MyPtr() : ptr( 0 ) {}
MyPtr( const MyPtr& other ) : ptr( other.ptr ) { if( ptr != 0 ) ptr->AddRef(); }
MyPtr( T* p ) : ptr( p ) { assert( p != 0 ); ptr->AddRef(); }
~MyPtr() { if( ptr != 0 ) ptr->Release(); }
// Внимание!!! Тип аргумента оператора присванивания такой не случайно!!!void operator = ( MyPtr other ) { Swap( other ); }
void Swap( MyPtr& other ) { srd::swap( ptr, other.ptr ); }
operator T* () const { return ptr; }
private:
T* ptr;
};
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, Ilias, Вы писали:
I>>Попробую еще раз. Есть некий объект, с некими действиями, которыми с ним можно производить. I>>Объекты эти могут быть нескольких разных типов (строка, число и т.д.) I>>В одной и той же функции для объекта типа строка и объекта типа число может быть (и будет!) разный код. I>>Т.е. например в setVal(string& val) надо проверять, чтобы в строке не было знаков '-', а в setVal(int& val) надо проверять, чтобы val нацело делилось на 7. I>>Дальше, после того как определены типы объектов, надо завести несколько конкретных объектов заданного типа. Например объект типа строка с начальным значением этой строки "blablabla", или объект типа число с начальным значением 16. I>>Причем желательно чтобы конкретный объект типа число с начальным значением 16 и то же самое с начальным значением 17 были разными типами.
E>А как ты планируешь звать SetValue у базы, еслине знаешь тип аргкмента? Как ты себе мыслишь клиентский код?
Ну про это я уже повыше написал, что ступил и такого не надо. В смысле не надо чтобы они хранились где-нибудь в векторе указателей на базовый класс.
Тут, похоже, последний момент остался не освещенным. Вот этот
I>>Причем желательно чтобы конкретный объект типа число с начальным значением 16 и то же самое с начальным значением 17 были разными типами.
Здравствуйте, Ilias, Вы писали:
I>Так вообще можно сделать?
Конечно можно.
1) Если речь идёт о числах, то можно так:
template<int I> struct UniqueType { enum { Val = I }; };
2) Если речь идёт о более сложных объектах, то сделать так, чтобы отслеживалась разность значений не получится, зато получится сделать так, чтобы каждому глобальному экземпляру твоих данных сопоставлялся в отдельный тип.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском