#c #templates #inheritance #compiler-errors #pragma
#c #шаблоны #наследование #ошибки компилятора #pragma
Вопрос:
Это довольно общий вопрос о стиле и безопасности при написании базового класса шаблона на C . Потерпите, однако, в конце есть конкретный вопрос…
У меня есть базовый класс шаблона, который полностью реализует требуемую функциональность, когда тип T является примитивным типом (int, float и т.д.). Однако, если T не является примитивным (например, если T является классом, которому необходимо вызвать конструктор с определенным набором аргументов), то некоторые методы необходимо будет переопределить, и по этой причине они объявлены виртуальными в шаблоне. Шаблон также содержит чисто виртуальный метод, который заставляет его быть абстрактным, поэтому для использования он должен быть производным от: производный класс, основанный на примитивном типе, может использовать все предоставленные методы и переопределять только чисто виртуальный, в то время как производный класс, основанный на непримитивном типе, должен переопределять все виртуальные методы.
Например:
template <typename T> class myTemplate
{
public:
// Constructor:
myTemplate<T>();
// Destructor:
virtual ~myTemplate();
// General function, valid for all classes derived from this:
void printMe()
{
printf("Hello, I'm from the templatern");
}
// Primitive function, must be overridden for non-primitive types:
virtual T DoSomething(const Tamp; myClass)
{
T result = result static_cast<T>(1);
return resu<
}
// Primitive function, must be overridden for non-primitive types:
virtual T DoSomethingElse(const Tamp; myClass)
{
T result = result - static_cast<T>(1);
return resu<
}
// Pure Virtual function, must be overridden in all cases:
virtual void RefrainFromDoingAnythingAtAll(const Tamp; myClass) = 0
};
class myIntegerClass : public myTemplate<int>
{
public:
virtual void RefrainFromDoingAnythingAtAll(const intamp; myInteger) {}
};
Предположим, теперь я хочу создать производный класс, в котором я ожидаю вызова ‘doSomething’, но не могу предусмотреть никаких обстоятельств, при которых ‘doSomethingElse’ был бы полезен. Поэтому я хотел бы переопределить ‘doSomething’, но не заморачиваться с ‘doSomethingElse’. Тем не менее, если в какой-то момент метод ‘doSomethingElse’ производного класса действительно вызывается (либо потому, что я действительно хотел вызвать ‘doSomething’, но по ошибке написал не то, или потому, что возникли обстоятельства, которые я ранее не смог предусмотреть), то я хочу, чтобы было выдано предупреждение компилятора, напоминающее мне, что я не могу этого сделать, если сначала я не переопределю ‘doSomethingElse’.
Например:
class myWatermelonClass : public myTemplate<Watermelon>
{
public:
virtual Watermelon DoSomething(const Watermelon amp;myWatermelon)
{
// Just return a copy of the original:
Watermelon anotherWatermelon(myWatermelon);
return anotherWatermelon;
}
virtual Watermelon DoSomethingElse(const Watermelon amp;myWatermelon)
{
// This routine shouldn't ever get called: if it does, then
// either I've made an error or, if I find that I really need
// to reimplement it after all, then I'd better buckle down
// and do it.
< How do I make the compiler remind me of this? >
}
virtual void RefrainFromDoingAnythingAtAll(const Watermelonamp; myWatermelon) {}
};
Очевидно, я знаю о стандартных директивах компилятора #error и #warning, но если я использую одну из них, то ошибка (или предупреждение) помечается при каждой компиляции. Я хочу убедиться, что ошибка выдается во время компиляции, если я небрежно вызываю
DoSomethingElse(aSpecificWatermelon);
откуда-то из моего кода, но не иначе. Есть ли способ добиться этого? Или это просто изначально неудачный дизайн?
Комментарии:
1. Вы проверяли чисто виртуальные методы?
2. «производный класс, основанный на примитивном типе, может использовать все предоставленные методы и переопределять только чисто виртуальный, в то время как производный класс, основанный на непримитивном типе, должен переопределять все виртуальные методы» — похоже, вам следует использовать отдельный класс, унаследованный от MyTemplate для ваших непримитивных типов. В mytemplate все виртуальные файлы должны быть чистыми, а все функции, кроме одной, должны иметь реализацию в новом классе.
Ответ №1:
Я думаю, вы неправильно используете шаблоны и виртуальную диспетчеризацию. Например, ваш базовый класс не собирается компилироваться для любого типа, который не поддерживает оператор сложения и может быть сконструирован из int. Каждый раз, когда myTemplate
неявно становится специализированным, все виртуальные функции будут скомпилированы, даже если вы переопределите их в производных классах:
virtual T DoSomething(const Tamp; myClass)
{
T result = result static_cast<T>(1); // compile error when T == Watermelon
return resu<
}
В вашем случае то, что вы ищете, — это явная специализация шаблона:
template <>
class myTemplate<Watermelon>
{
public:
// General function, valid for all classes derived from this:
void printMe()
{
printf("Hello, I'm from the Watermelon template specialisationrn");
}
virtual Watermelon DoSomething(const Watermelon amp;myWatermelon)
{
// Just return a copy of the original:
Watermelon anotherWatermelon(myWatermelon);
return anotherWatermelon;
}
// notice there's no DoSomethingElse!
virtual void RefrainFromDoingAnythingAtAll(const Watermelonamp; myWatermelon) {}
};
Теперь вызов DoSomethingElse
экземпляра myTemplate<Watermelon>
немедленно выдаст вам ошибку компиляции, поскольку такой функции нет.
Комментарии:
1. Я думаю, что если бы мой реальный класс более походил на приведенный мной пример, то это был бы правильный путь. Однако на практике мой фактический ‘MyTemplate’ не содержит static_casts в (боюсь, это были первые вещи, которые пришли мне на ум в качестве примера чего-то, что нужно было бы переопределить для непримитивных типов), но имеет очень большое количество элементов (подавляющее большинство из которых не нужно переопределять) и полей данных, которые (когда я только что проводил эксперимент) не были унаследованы от универсального шаблона по специализированному шаблону.
2. Тогда у вашего
myTemplate
есть виртуальные члены?3. Да, это так, но они составляют незначительное меньшинство от общего числа членов. Подавляющее большинство функций-членов не нуждаются в переопределении, поэтому я не хочу переопределять их в моих специализированных шаблонах или в конечных производных классах.
4. Затем разложите базовый тип.
myTemplateBase<T>
может содержать элементы данных и функции, не нуждающиеся в переопределении.myTemplate<T>
иmyTemplate<Watermelon>
могут быть как производными от него, так и предоставлять конкретные функции или реализовывать виртуальные.5. Я знаю, что это сработало бы, но это более громоздкое решение, чем я надеялся. Я надеялся, что в языке есть что-то, что упростит это, но, похоже, этого нет.
Ответ №2:
Используя static_assert
, вы можете вызвать ошибку компиляции при попытке вызвать функцию, которая не соответствует определенным критериям. Виртуальные функции вообще не нужны. Вот пример:
#include <iostream>
#include <type_traits>
using std::cout;
template <typename T>
struct myTemplate {
void printMe() { cout << "Hello, I'm from the templatern"; }
T DoSomething(const Tamp; myClass)
{
static_assert(
std::is_fundamental<T>::value,
"DoSomething must be redefined in derived classes "
"for non-fundamental types."
);
T result = myClass static_cast<T>(1);
return resu<
}
T DoSomethingElse(const Tamp; myClass)
{
static_assert(
std::is_fundamental<T>::value,
"DoSomethingElse must be redefined in derived classes "
"for non-fundamental types."
);
T result = myClass - static_cast<T>(1);
return resu<
}
template <typename U>
struct never_true { static const bool value = false; };
void RefrainFromDoingAnythingAtAll(const Tamp;)
{
static_assert(
never_true<T>::value,
"RefrainFromDoingAnythingAtAll must be redefined "
"in derived classes."
);
}
};
struct Watermelon {
};
struct Lemon {
};
struct myIntegerClass : myTemplate<int> {
void RefrainFromDoingAnythingAtAll(const int amp;) { }
};
struct myWatermelonClass : myTemplate<Watermelon> {
Watermelon DoSomething(const Watermelonamp;)
{
return Watermelon();
}
Watermelon DoSomethingElse(const Watermelonamp;)
{
return Watermelon();
}
void RefrainFromDoingAnythingAtAll(const Watermelon amp;) { }
};
struct myLemonClass : myTemplate<Lemon> {
};
int main()
{
myIntegerClass x;
x.DoSomething(5); // works
x.DoSomethingElse(5); // works
x.RefrainFromDoingAnythingAtAll(5); // works
myWatermelonClass y;
y.DoSomething(Watermelon()); // works
y.DoSomethingElse(Watermelon()); // works
y.RefrainFromDoingAnythingAtAll(Watermelon()); // works
myLemonClass z;
z.DoSomething(Lemon()); // error
z.DoSomethingElse(Lemon()); // error
z.RefrainFromDoingAnythingAtAll(Lemon()); // error
}
Комментарии:
1. Примечание: это работает, только если
DoSomethingElse
не является виртуальной функцией. В противном случае утверждение дляmyTemplate<Watermelon>
завершится ошибкой.2. Мне нравится внешний вид этого подхода static_assert, но я хочу опробовать его, прежде чем принимать ответ. Комментарий @gwiazdorrr был бы совершенно верным, если бы моя реальная шаблонная функция doSomethingElse содержала static_cast , но в моем реальном шаблоне этого нет.
3. К сожалению, я не могу заставить это работать, поскольку даже когда элемент переопределен, static_assert по-прежнему выдает ошибку компилятора. Я предполагаю, что в этом значение замечания, сделанного в ответе @gwiazdorrr: «каждый раз, когда MyTemplate становится специализированным неявно, все виртуальные функции будут скомпилированы, даже если вы переопределите их в производных классах», а также его комментарий выше об этой схеме, работающей только в том случае, если doSomethingElse не является виртуальным.