Переопределение принудительного метода C в конкретном классе

#c #abstract-class

#c #абстрактный класс

Вопрос:

Есть ли способ в C написать конкретный класс, который, когда другой класс является производным от него, имеет метод, который должен быть переопределен. Абстрактный класс позволяет принудительно использовать производный класс для создания конкретных версий любых абстрактных методов, но мне нужен базовый класс, который обеспечивает это, но также может использоваться сам по себе. Я знаю, что абстрактный метод также может указывать функциональность по умолчанию, но он по-прежнему остается абстрактным классом, экземпляр которого не может быть создан.

Я также посмотрел шаблон метода шаблона, но, похоже, это тоже не совсем то, что я ищу.

Комментарии:

1. Ищете сбой во время компиляции? Сбой во время выполнения тривиально легко реализовать.

2. @Chad: как же так? Мне любопытно, я не вижу этого прямо сейчас…

3. чтобы сделать это во время выполнения: базовый класс имеет виртуальную функцию, которая генерирует исключение, например NotImplementedException Чтобы программа работала должным образом, вам нужно переопределить эту функцию, в противном случае исключение генерируется из функции в базовом классе.

4. @piokuc: нет, мы тоже хотим рабочую реализацию в базе. Но подождите, я думаю, я понял: мы можем проверить typeid(*this) == typeid(*dynamic_cast<void*>(this)) , верно?

5. Почему вам нужно, чтобы «абстрактный» базовый класс был конкретным?

Ответ №1:

Я предполагаю, что вы ищете принудительное выполнение этого условия во время компиляции (спасибо @ Chad за указание на это)

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

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

 class BaseButConcrete
{
    ... //Common stuff
    ... //

    virtual void foo()
    { /*implementation that you do not want to be inherited, but will be...*/ }
}

class DerivedOtherConcrete : public BaseButConcrete
{
    void foo()
    { /*different implementation, 
      but no obligation from compiler point of view*/ }
}
  

Я не вижу веской дизайнерской причины, по которой все обычные вещи не могли быть перемещены в абстрактный базовый класс. Из того, что вы описываете, вы не хотите наследовать реализацию foo в Derived, поэтому не наследуйте эту часть! Таким образом, классический дизайн должен сработать :

 class AbstractBase
{
    ... //Common stuff has moved here
    ... //
    virtual void foo() =0;
}

class NotAnymoreBaseButStillConcrete : public AbstractBase
{
    void foo()
    { /*implementation that should not be inherited,
      and that will not by design*/ }
}

class DerivedOtherConcrete : public AbstractBase
{
    void foo()
    { /*different implementation, 
      now mandatory compiler-wise for the class to be concrete*/ }
}
  

Таким образом, общий материал по-прежнему является общим для всех ваших производных классов, и вы сохраняете то, что вы не хотите наследовать (т. Е. Реализацию foo), разделенную в классах, не находящихся на одном пути наследования.

Комментарии:

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

2. @TheTrowser В случае clone() метода я не думаю, что в методе базового класса будет общий фрагмент кода. В базовом классе clone() был бы чисто виртуальный метод, заставляющий все производные классы его реализовывать.

3. это нормально, но как насчет следующего уровня иерархии? Например, InterfaceClonable <- MyClass <- MySecondClass. В MySecondClass не было бы принудительного средства для реализации clone. Поэтому мы снова приветствуем нарезку.

4. @TheTrowser Это интересный вопрос, но я бы сказал, что он сильно отличается от того, который задал Джоннстер.

5. Я думаю, что это именно то, о чем здесь спрашивают: «Есть ли способ в C написать конкретный класс, который, когда другой класс является производным от него, имеет метод, который должен быть переопределен». У меня есть конкретный класс X с реализацией метода clone(), но я также хочу, чтобы любой другой класс также реализовывал его.

Ответ №2:

Нет, это невозможно. Если вы хотите принудительно использовать (во время компиляции) производный класс для определения виртуальной функции, то его базовый класс должен быть абстрактным, или, другими словами, базовый класс должен иметь чисто виртуальную функцию. Абстрактный класс (с чисто виртуальными функциями) не может быть конкретным, т. Е. вы не можете создать его экземпляр.

Ответ №3:

Один из вариантов — поместить всю реализацию класса в абстрактный суперкласс и наследовать от него вместо этого.

  • Переместите всю реализацию вашего конкретного класса T в абстрактный класс S.
  • В S сделайте метод, который должен быть переопределен, чисто виртуальной функцией — и предоставьте его определение.
  • T подклассы S.
  • В T переопределите функцию, которую необходимо переопределить, и вызовите реализацию S.
  • Вместо подкласса T, подкласс S.
  • Предотвращение подкласса T.

Ответ №4:

Как упоминали другие, вы можете «исправить» это для одного уровня деривации, разделив ваш текущий конкретный класс на абстрактный класс и конкретный класс.

Нередким обобщением этого подхода является наличие шаблона класса, который должен использоваться для создания конкретного класса из каждого абстрактного класса. Например, он может предоставлять clone метод. Или, в ATL от Microsoft, он предоставляет IUnknown реализацию интерфейса.

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

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

Ответ №5:

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

 #include <iostream>

struct Base{
  virtual void print() {std::cout << "Base" << std::endl; }  
};

struct RequireOverride {
  virtual void print() = 0;   
};

struct D1: Base, RequireOverride {
  virtual void print() {std::cout << "D1" << std::endl; }  
};

struct D2: Base, RequireOverride {
  // no override of function print() results in a compilation error
  // when trying to instantiate D2
};


int main() {
  D1 d1;
  d1.print(); //ok


  //D2 d2; // ERROR: cannot declare variable 'd2' to be of abstract type 'D2'
           // because the following virtual functions are pure within 'D2':
           // virtual void RequireOverride::print()
  return 0;
}
  

Если у вас есть несколько методов, для которых вы хотите принудительно использовать переопределения, вы можете поместить их все в RequireOverride или определить дополнительные структуры для множественного наследования, такие как RequireOverrideMethod1 , RequireOverrideMethod2 , в соответствии с контекстом.