Здравствуйте, Голощапов Александр, Вы писали:
ГА>Привет 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?