Как я могу принудительно выдать ошибку компилятора, если вызывается переопределенный виртуальный метод?

#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 не является виртуальным.