Определите во время компиляции, был ли виртуальный метод переопределен - PullRequest
3 голосов
/ 15 марта 2020

Предположим, у меня есть некоторый класс A:

a.h:
class A {
virtual unsigned foo() {
   return 0;
}

Затем у меня есть много классов (назовем эти промежуточные звенья), наследующих от A (класс B, класс C ... et c) , Эти классы НИКОГДА не переопределят foo ().

b.h
class B : pubic A{
bool override_foo = true;

 ....
}

c.h:
class C : pubic A{
 bool override_foo = false;
}

Тогда у меня есть много классов, наследуемых от промежуточных. Класс D, E и др. c. Только один, если они когда-либо будут наследоваться от одного промежуточного звена, т. Е. Это соотношение 1: 1 между этими классами и промежуточными звеньями. Гарантируется, что только (и всегда) будет один из этих классов, унаследованный от одного из промежуточных.

Теперь эти классы МОГУТ переопределять foo. Однако, если они это делают, и промежуточный класс, от которого они наследуют, не определяет override_foo как true, мне нужно, чтобы компиляция провалилась.

Так, например, если у нас есть класс D:

d.h:
class D : pubic B{
 unsigned foo() override();
}

Компиляция прошла бы нормально, так как B имеет override_foo, определенное как true.

Однако, нам это не удастся:

e.h:
class E : pubic C{
 unsigned foo() override();
}

Так как C имеет override_foo как false.

Это что-то достижимое? К сожалению, в этой проблеме очень мало гибкости. Я работаю в компании, где промежуточные классы (B, C) могут быть изменены очень незначительно. Они созданы из компиляции частной структуры. Я мог бы добавить к ним члена или любого другого макро-мага c. То же самое для базового класса A. Однако классы внуков, D, E, определены другими инженерами для переопределения частей инфраструктуры, и мне нужно запретить им это делать, если у промежуточных классов нет некоторых ключевых атрибутов.

Спасибо!

Ответы [ 3 ]

2 голосов
/ 15 марта 2020

Вы можете использовать ключевое слово final, чтобы запретить унаследованные классы для переопределения метода.

class A {
public:
  virtual ~A() = default;
  virtual void foo() {}
};

class B : public A {
public:
  void foo() final { A::foo(); }
};

class C : public A {};

class D : public B {
public:
  void foo() override;  // <- compilation error here
};

class E : public C {
public:
  void foo() override;
};

int main() {}
2 голосов
/ 16 марта 2020

Другие ответы требуют, чтобы ваши промежуточные классы переопределяли версию foo () базового класса на final, чтобы производный класс не мог переопределить ее, вместо того, чтобы заставить логическую переменную-член определять результат - и это сработало бы. И, возможно, это то, что вы хотите, но это не то, что вы сказали, что хотели. Поэтому я собираюсь ответить на вопрос, который вы задали, даже если другие ответы могут быть более точными, чем вы на самом деле представляли.

Если вы можете изменить B, то это делает производные классы довольно простыми для записи:

#include <stdio.h>
#include <type_traits>

struct B {
    virtual int foo() {
        puts("B");
    };

    template<typename X>
    using baseFooType = typename std::enable_if<X::override_foo,decltype(((B*)nullptr)->foo())>::type;
};

struct C : public B {
    constexpr static bool override_foo = true;
};

struct D : public B {
    constexpr static bool override_foo = true;
};

struct E : public C {
    baseFooType<C> foo() override {
        puts("E");
    }
};

struct F : public D {
    baseFooType<D> foo() override {
        puts("F");
    }
};

int main()
{
    E().foo();
    F().foo();
}

Попробуйте здесь: https://onlinegdb.com/rJe5jXQhHI

Если вы не можете изменить режим B, вы можете использовать вспомогательный класс, например:

#include <stdio.h>
#include <type_traits>

struct B {
    virtual int foo() {
        puts("B");
    };
};

template<typename X>
struct baseFooType {
    using type = typename std::enable_if<X::override_foo,decltype(((B*)nullptr)->foo())>::type;
};

struct C : public B {
    constexpr static bool override_foo = true;
};

struct D : public B {
    constexpr static bool override_foo = true;
};

struct E : public C {
    baseFooType<C>::type foo() override {
        puts("E");
    }
};

struct F : public D {
    baseFooType<D>::type foo() override {
        puts("F");
    }
};

int main()
{
    E().foo();
    F().foo();
}

Попробуйте это здесь: https://onlinegdb.com/BJXg4QhB8

Примечание: я использовал constexpr в примере, потому что я думал, что его нужно будет передать как параметр шаблона, но оказывается, что он не нужен - вместо этого вы можете использовать const.

0 голосов
/ 15 марта 2020

Вы можете делать все, что просите, с помощью ключевого слова final (c ++ 11).

Как и в других языках, ключевое слово final предотвращает перегрузку метода в производном классе или может быть используется для предотвращения наследования класса от altoghether!

Например

class A: public X {

  void foo() final; // <-- Overrides foo() in X

};

class B: public A {

  void foo() override; // <-- Compile error! foo() has been declared final!

};

Это работает во время компиляции и выдаст вам хорошую ошибку компиляции :) Final также подразумевает virtual и override (если что-то отменяет).

...