Удаление виртуальной функции из производного класса

У меня есть функция виртуального базового класса, которая никогда не должна использоваться в определенном производном классе. Есть ли способ "удалить" его? Я могу, конечно, просто дать ему пустое определение, но я бы предпочел сделать его попытку использования, выбросив ошибку времени компиляции. Спецификатор delete С++ 11 выглядит так, как хотелось бы, но

class B
{
    virtual void f();
};

class D : public B
{
    virtual void f() = delete; //Error
};

не будет компилироваться; gcc, по крайней мере, явно не позволит мне удалить функцию, которая имеет неиспользуемую базовую версию. Есть ли другой способ получить такую же функциональность?

Ответ 1

Стандарт не допускается, однако вы можете использовать одно из следующих двух обходных решений, чтобы получить аналогичное поведение.

Первым было бы использовать using чтобы изменить видимость метода для частного, тем самым не позволяя другим использовать его. Проблема с этим решением заключается в том, что вызов метода на указателе суперкласса не приводит к ошибке компиляции.

class B
{
public:
    virtual void f();
};

class D : public B
{
private:
    using B::f;
};

Лучшим решением, которое я нашел до сих пор, чтобы получить ошибку времени компиляции при вызове метода D является использование static_assert с общей структурой, которая наследует от false_type. Пока никто не называет метод, структура остается неопределенной, и static_assert не будет терпеть неудачу.

Если метод вызывается, то структура определена и ее значение ложно, поэтому static_assert терпит неудачу.

Если метод не вызывается, но вы пытаетесь вызвать его на указателе суперкласса, то метод D не определяется, и вы получаете undefined reference ошибку компиляции undefined reference.

template <typename T>
struct fail : std::false_type 
{
};

class B
{
public:
    virtual void f() 
    {
    }
};

class D : public B
{
public:
    template<typename T = bool>
    void
    f()
    {
        static_assert (fail<T>::value, "Do not use!");
    }
};

Другим обходным решением было бы выбросить исключение при использовании этого метода, но это только запустило бы во время выполнения.

Ответ 2

Стандарт не позволяет удалить какой-либо член базового класса в производном классе по уважительной причине:
Это нарушает наследование, в частности отношения "is-a".

По связанным причинам он не позволяет производному классу определять функцию, удаленную в базовом классе:
Крюк больше не является частью контракта базового класса, и поэтому он не позволяет вам полагаться на предыдущие гарантии, которые больше не сохраняются.

Если вы хотите получить хитрость, вы можете заставить ошибку, но это будет время ссылки вместо времени компиляции:
Объявите функцию-член, но никогда не определяйте ее (это не гарантирует 100% работу для виртуальных функций).
Лучше также взглянуть на устаревший атрибут GCC для более ранних предупреждений __attribute__ ((deprecated)).
Для получения подробной информации и аналогичной магии MS: C++ отмечен как устаревший

Ответ 3

"У меня есть функция виртуального базового класса, которая никогда не должна использоваться в определенном производном классе".

В некоторых отношениях это противоречие. Весь смысл виртуальных функций заключается в предоставлении различных реализаций контракта, предоставляемых базовым классом. То, что вы пытаетесь сделать, это разорвать контракт. Язык C++ предназначен для предотвращения этого. Вот почему он заставляет вас реализовывать чистые виртуальные функции при создании экземпляра объекта. И именно поэтому он не позволит вам удалить часть контракта.

То, что происходит, - это хорошо. Вероятно, это мешает вам реализовать неправильный выбор дизайна.

Однако:

Иногда бывает целесообразно иметь пустую реализацию, которая ничего не делает:

void MyClass::my_virtual_function()
{
    // nothing here
}

Или пустая реализация, которая возвращает статус "сбой":

bool MyClass::my_virtual_function()
{
    return false;
}

Все зависит от того, что вы пытаетесь сделать. Возможно, если бы вы могли дать больше информации о том, что вы пытаетесь достичь, кто-то может указать вам в правильном направлении.

РЕДАКТИРОВАТЬ

Если вы думаете об этом, чтобы избежать вызова функции для определенного производного типа, вызывающему нужно знать, какой тип он вызывает. Весь смысл вызова ссылки/указателя базового класса заключается в том, что вы не знаете, какой производный тип получит вызов.

Ответ 4

То, что вы можете сделать, просто бросает исключение в производную реализацию. Например, структура Java Collections делает это довольно чрезмерно: когда операция обновления выполняется в неизменяемой коллекции, соответствующий метод просто выдает UnsupportedOperationException. Вы можете сделать то же самое в C++.

Конечно, это покажет вредоносное использование функции только во время выполнения; а не во время компиляции. Однако с помощью виртуальных методов вы не можете поймать такие ошибки во время компиляции в любом случае из-за полиморфизма. Например:

B* b = new D();
b.f();

Здесь вы храните D в переменной B*. Таким образом, даже если бы был способ сообщить компилятору, что вам не разрешено вызывать f на D, компилятор не сможет сообщить об этой ошибке здесь, потому что он видит только B

Ответ 5

У меня есть функция виртуального базового класса, которая никогда не должна использоваться в определенном производном классе.

С++ 11 предоставляет ключевое слово final которое предотвращает переопределение виртуальной функции.

Посмотрите: http://en.cppreference.com/w/cpp/language/final.

class B
{
  virtual void f() final;
};

class D : public B
{
  // virtual void f();  // a compile-time error
  // void f() override; // a compile-time error
  void f(); // non-virtual function, it ok
};