Здравствуйте, Голощапов Александр, Вы писали:
ГА>Привет ALL!
ГА>допустим есть такая ситуация:
ГА>
ГА>class A {
ГА>//...
ГА>};
ГА>class B : public A {
ГА>//...
ГА>};
ГА>template<class T> class C {
ГА>//...
~C()
{
sizeof(static_cast<A*>(reinterpret_cast<T*>(0)));
}
ГА>}
ГА>
ГА>Каким образом можно ограничить использование класса C, чтобы в качестве аргумента шаблона T можно было использовать только наследников A ?
ГА>С уважением
class A {
//...
};
class B : public A {
//...
};
class E
{
};
template<class T> class C {
//...public:
~C() { T* ptr_t = NULL; A* ptr_a = ptr_t; //Добавить внятный коментарий}
};
int main(int argc, char** argv)
{
C<A> ca;
C<B> cb;
C<E> ce;//error C2440: 'initializing' : cannot convert from 'class E *' to 'class A *'
//Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast
//or function-style castreturn 0;
}
class A
{
public:
typedef int IAmGood;
};
class B : public A
{
};
class X {};
template <class T> class C
{
public:
~C() { typedef T::IAmGood isGood; }
};
int main()
{
C<A> ca;
C<B> cb;
C<X> cx;
return 0;
}
ГА>Общий смысл понятен, спасибо. А почему именно в деструкторе?
Несколько причин:
1) компилятор в любом случае генерирует код для деструктора
2) код проверки локализуется в одном месте. Можно было поступить иначе — создать статическую функцию с проверкой ограничений и вызывать ее в каждом конструкторе, но это менее надежное решение (вдруг при написании кода конструктора мы забудем, что нужно вызывать функцию проверки ограничений).
Здравствуйте, Анатолий Широков, Вы писали:
АШ>Здравствуйте, Дмитрий Наумов, Вы писали:
ДН>>Здравствуйте, Анатолий Широков, Вы писали:
ДН>>А может без рантайма обойдемся?
АШ>О каком рантайме идет речь? Проверка осуществляется в компайлтайме.
Тьфу, я сам не знаю с чего я там "рантайм" написал — хотел написать "каст", но руки блин, совсем одемократились... Сорри. Читать как "А может без каста обойдемся?" Просто как то некрасиво, чисто имхо, два каста в одной строке как то некрасиво...
Здравствуйте, Анатолий Широков, Вы писали:
ГА>>Общий смысл понятен, спасибо. А почему именно в деструкторе?
АШ>Несколько причин:
АШ>1) компилятор в любом случае генерирует код для деструктора АШ>2) код проверки локализуется в одном месте. Можно было поступить иначе — создать статическую функцию с проверкой ограничений и вызывать ее в каждом конструкторе, но это менее надежное решение (вдруг при написании кода конструктора мы забудем, что нужно вызывать функцию проверки ограничений).
А можно было бы так (посмотрите мой пример):
template C<X>;
что вызовет явное инстанциирование шаблона и тогда не надо код проверки ни откуда вызывать. При явном инстанциировании инстанциируются все методы шаблона.
Здравствуйте, Дмитрий Наумов, Вы писали:
ДН>Здравствуйте, Анатолий Широков, Вы писали:
АШ>Здравствуйте, Дмитрий Наумов, Вы писали:
ДН>>Здравствуйте, Анатолий Широков, Вы писали:
ДН>>А может без рантайма обойдемся?
АШ>О каком рантайме идет речь? Проверка осуществляется в компайлтайме.
ДН>Тьфу, я сам не знаю с чего я там "рантайм" написал — хотел написать "каст", но руки блин, совсем одемократились... Сорри. Читать как "А может без каста обойдемся?" Просто как то некрасиво, чисто имхо, два каста в одной строке как то некрасиво...
А чем же это не красиво? Ведь ничего запрещенного код с кастами не делает. Опять же, оптимизатор этот код благополучно выкинет. А вот то, что ты меняешь интерфейс класс A только ради того, чтобы проверить "наследник или нет" заставляет задуматься над правильностью твоего решения, имхо.
Здравствуйте, Анатолий Широков, Вы писали:
АШ>А чем же это не красиво? Ведь ничего запрещенного код с кастами не делает. Опять же, оптимизатор этот код благополучно выкинет. А вот то, что ты меняешь интерфейс класс A только ради того, чтобы проверить "наследник или нет" заставляет задуматься над правильностью твоего решения, имхо.
Ну если считать что trait это "изменение интерфейса", то ...
Я ж свое имхо высказал, что касты, особенно reinterpret_cast уж больно глаза режет.
ДН>А можно было бы так (посмотрите мой пример): ДН>
ДН>template C<X>;
ДН>
ДН>что вызовет явное инстанциирование шаблона и тогда не надо код проверки ни откуда вызывать. При явном инстанциировании инстанциируются все методы шаблона.
Да это же самоубийство!!!. Представь шаблон из 1000 методов.
Здравствуйте, Анатолий Широков, Вы писали:
ДН>>А можно было бы так (посмотрите мой пример): ДН>>
ДН>>template C<X>;
ДН>>
ДН>>что вызовет явное инстанциирование шаблона и тогда не надо код проверки ни откуда вызывать. При явном инстанциировании инстанциируются все методы шаблона.
АШ>Да это же самоубийство!!!. Представь шаблон из 1000 методов.
Опять ты в крайности ударяешься... Я предложил вариант к действию, а не инструкцию как делать ВСЕГДА.
ДН>Опять ты в крайности ударяешься... Я предложил вариант к действию, а не инструкцию как делать ВСЕГДА.
Никаких крайностей. Твое решение нельзя рассматривать даже как вариант, потому что:
1) для обеспечения проверки ты изменяешь интерфейс класса A внося в него новое "свойство" (подставь вместо A std::istream_iterator и ты поймешь к чему я клоню)
2) для работоспособности твоего варианта необходимо явное инстанцирование (что само по себе рассточительно). Но представь, я забываю явно инстанцировать C<my_class>, причем my_class не является потомком A, и что? Да ничего. Класс С преспокойно берет my_class и начинает с ним работать (поскольку T::IsGood не будет генерироваться).
Здравствуйте, Анатолий Широков, Вы писали:
ДН>>Опять ты в крайности ударяешься... Я предложил вариант к действию, а не инструкцию как делать ВСЕГДА.
АШ>Никаких крайностей. Твое решение нельзя рассматривать даже как вариант, потому что:
АШ>1) для обеспечения проверки ты изменяешь интерфейс класса A внося в него новое "свойство" (подставь вместо A std::istream_iterator и ты поймешь к чему я клоню)
С этим я еще могу согласится, но только в случае если А — класс из библиотеки(или еще откуда нибудь), который мы не можем модифицировать.
АШ>2) для работоспособности твоего варианта необходимо явное инстанцирование (что само по себе рассточительно). Но представь, я забываю явно инстанцировать C<my_class>, причем my_class не является потомком A, и что? Да ничего. Класс С приспокойно берет my_class и начинает с ним работать (посколь T::IsGood не будет генерироваться).
АШ>
Оставь этот typedef в деструкторе и "забывчивость" не будет виновником появления ошибок.
ДН>С этим я еще могу согласится, но только в случае если А — класс из библиотеки(или еще откуда нибудь), который мы не можем модифицировать.
А так нам ничего не надо модифицировать.
АШ>2) для работоспособности твоего варианта необходимо явное инстанцирование (что само по себе рассточительно). Но представь, я забываю явно инстанцировать C<my_class>, причем my_class не является потомком A, и что? Да ничего. Класс С приспокойно берет my_class и начинает с ним работать (посколь T::IsGood не будет генерироваться).
АШ>
ДН>Оставь этот typedef в деструкторе и "забывчивость" не будет виновником появления ошибок.
Ну тогда и явного инстанцирования не надо будет, но по причине первой второе можно уже и не рассматривать.
Здравствуйте, Анатолий Широков, Вы писали:
ДН>>С этим я еще могу согласится, но только в случае если А — класс из библиотеки(или еще откуда нибудь), который мы не можем модифицировать.
АШ>А так нам ничего не надо модифицировать.
АШ>>2) для работоспособности твоего варианта необходимо явное инстанцирование (что само по себе рассточительно). Но представь, я забываю явно инстанцировать C<my_class>, причем my_class не является потомком A, и что? Да ничего. Класс С приспокойно берет my_class и начинает с ним работать (посколь T::IsGood не будет генерироваться).
АШ>>
ДН>>Оставь этот typedef в деструкторе и "забывчивость" не будет виновником появления ошибок.
АШ>Ну тогда и явного инстанцирования не надо будет, но по причине первой второе можно уже и не рассматривать.
Ну раз ты про стримы вспомнил, начнем грязную войну — берем следующий код, пытаемся компилить, потом комментируем в деструкторе строку с кастами и раскомментируем с тайпдефом, компилируем. Потом думаем о том какое решение лучше. (мое имхо — каждое хорошо в определенной ситуации)
class A
{
public:
typedef int Derived_From_A;
int a;
};
class B : public A { int b; };
class DA : public A {};
class DB : public B {};
class X : virtual public DA, virtual public DB {};
template <class T> class C
{
public:
~C()
{
//typedef T::Derived_From_A isGood;sizeof(static_cast<A*>(reinterpret_cast<T*>(0)));
}
};
int main()
{
C<X> cx;
return 0;
}
ДН>Ну раз ты про стримы вспомнил, начнем грязную войну — берем следующий код, пытаемся компилить, потом комментируем в деструкторе строку с кастами и раскомментируем с тайпдефом, компилируем. Потом думаем о том какое решение лучше. (мое имхо — каждое хорошо в определенной
ситуации)
Неверное использование virtual. Пожалуйста:
ДН>
ДН>class A
ДН>{
ДН>public:
ДН> typedef int Derived_From_A;
ДН> int a;
ДН>};
ДН>class B : virtualpublic A { int b; };
ДН>class DA : virtualpublic A {};
ДН>class DB : public B {};
ДН>class X : public DA, public DB {};
ДН>template <class T> class C
ДН>{
ДН>public:
ДН> ~C()
ДН> {
ДН> //typedef T::Derived_From_A isGood;
ДН> sizeof(static_cast<A*>(reinterpret_cast<T*>(0)));
ДН> }
ДН>};
ДН>int main()
ДН>{
C<X> cx;
ДН> return 0;
ДН>}
ДН>
Здравствуйте, Анатолий Широков, Вы писали:
АШ>Несколько причин: АШ>1) компилятор в любом случае генерирует код для деструктора
м... т.е. таким образом мы не обременим объект дополнительными функциями? или о чем речь?
АШ>2) код проверки локализуется в одном месте. Можно было поступить иначе — создать статическую функцию с проверкой ограничений и вызывать ее в каждом конструкторе, но это менее надежное решение (вдруг при написании кода конструктора мы забудем, что нужно вызывать функцию проверки ограничений).
т.е. что-то вроде этого ?
class A {
//...
};
class B : public A {
//...
};
template<class T> class C {
public:
static void Check() {sizeof(static_cast<A*>(reinterpret_cast<T*>(0)));}
C()
{
Check();
//...
}
};
Я тогда не очень понял что значит "вызывать ее в каждом конструкторе", ведь конструктор один. Или речь идет уже о производных классах от C?
#include <TypeManip.h>
class A {
};
class B : public A {
};
template <class T>
class C {
char check[SUPERSUBCLASS(A, T)];
};
int main(int argc, char* argv[])
{
C<B> good;
C<int> bad;
return 0;
}
PS в vc6 выдается варнинг "nonstandard extension used : zero-sized array" вместо ошибки
ГА>Я тогда не очень понял что значит "вызывать ее в каждом конструкторе", ведь конструктор один. Или речь идет уже о производных классах от C?
Число конструкторов не ограничено (то есть, потенциально может быть создано неограниченное число конструкторов класса и в каждый из них надо не забыть поместить вызов Check(), поскольку будет существовать столько путей создания объекта сколько конструкторов было объявлено (явно или не явно)), а деструктор всего один. Вот мы и использует этот факт и именно в деструкторе размещаем код проверки. Помимо этого, компилятор, инстанцируя шаблон, генерирует только необходимые методы (если, конечно, не указано явное инстанцирование), но так как стандарт гарантирует обязательный вызов деструктора объекта перед его уничтожением, то код деструктора будет генерироваться всегда, что нам и нужно для гарантии проверки требований.
Здравствуйте, Анатолий Широков, Вы писали:
АШ>Число конструкторов не ограничено (то есть, потенциально может быть создано неограниченное число конструкторов класса и в каждый из них надо не забыть поместить вызов Check(), поскольку будет существовать столько путей создания объекта сколько конструкторов было объявлено (явно или не явно)), а деструктор всего один. Вот мы и использует этот факт и именно в деструкторе размещаем код проверки. Помимо этого, компилятор, инстанцируя шаблон, генерирует только необходимые методы (если, конечно, не указано явное инстанцирование), но так как стандарт гарантирует обязательный вызов деструктора объекта перед его уничтожением, то код деструктора будет генерироваться всегда, что нам и нужно для гарантии проверки требований.
Здравствуйте, Дмитрий Наумов, Вы писали:
ДН>Здравствуйте, Анатолий Широков, Вы писали:
АШ>деструктор всего один. Вот мы и использует этот факт и именно в деструкторе размещаем код проверки
ДН>Интересно, а что будет если объект аллокирован на куче, а вызвыть delete для него пользователь забыл? Проверки не будет?
По-моему, undefined behavior по
3.8
4. A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which object occupied is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.
тут смысл не в том что это ill-formed или нет, понятно что это баг. Но, проблема в том, что одна ошибка программиста — забыл вызвать delete, повлечет еще одну, возможно более серьезную проблему — проверка на то, что шаблон параметризирован наследником от ... не будет выполнятся.
Здравствуйте, Дмитрий Наумов, Вы писали:
ДН>тут смысл не в том что это ill-formed или нет, понятно что это баг. Но, проблема в том, что одна ошибка программиста — забыл вызвать delete, повлечет еще одну, возможно более серьезную проблему — проверка на то, что шаблон параметризирован наследником от ... не будет выполнятся.
Почему? Проверка будет идти на этапе компиляции, когда дело дойдет до деструктора, который будет обязательно: либо самописный либо генеренный.
А уж вызывается этот деструктор где-нибудь или нет, имхо, дело десятое и на проверку не влияет.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, WFrag, Вы писали:
WF>enum { is_T_derived_from_A = boost::is_base_and_derived< A, T >::value }; WF>BOOST_STATIC_ASSERT( is_T_derived_from_A );
К>А чтобы не тащить весь boost, -- подсмотрите, как там это сделано и оставьте себе.
Здравствуйте, UgN, Вы писали:
UgN>Здравствуйте, Дмитрий Наумов, Вы писали:
ДН>тут смысл не в том что это ill-formed или нет, понятно что это баг. Но, проблема в том, что одна ошибка программиста — забыл вызвать delete, повлечет еще одну, возможно более серьезную проблему — проверка на то, что шаблон параметризирован наследником от ... не будет выполнятся.
UgN>Почему? Проверка будет идти на этапе компиляции, когда дело дойдет до деструктора, который будет обязательно: либо самописный либо генеренный. UgN>А уж вызывается этот деструктор где-нибудь или нет, имхо, дело десятое и на проверку не влияет.
Эксперимент показывает, что VC7 не инстанцирует деструктор, если он не вызывается.
Здравствуйте, Голощапов Александр, Вы писали:
ГА>Привет ALL!
ГА>допустим есть такая ситуация:
ГА>
ГА>class A {
ГА>//...
ГА>};
ГА>class B : public A {
ГА>//...
ГА>};
ГА>template<class T> class C {
ГА>//...
ГА>}
ГА>
ГА>Каким образом можно ограничить использование класса C, чтобы в качестве аргумента шаблона T можно было использовать только наследников A ?
ГА>С уважением
template<class Potomok, class Predok>
class isDerived
{
private:
//Определение не требуетсяstatic char discriminator(...);
static double discriminator(Predok*);
static Potomok* d;
public:
enum { value = ( sizeof(discriminator(d)) != sizeof(char) ) };
};
#define IS_DERIVED(Potomok, Predok) isDerived< Potomok, Predok >::value
//Ваш случай.template<class T> class C
{
private:
#ifdef _DEBUG
char blocker[IS_DERIVED(T, A)];
#endif
};
Всё считается на этапе компиляции. Правда работает только для public-наследования.