Наследование с неполным базовым классом

У меня вопрос относительно концепции. Во-первых, я инженер-механик, а не программист, поэтому у меня есть некоторые знания C++, но мало опыта. Я использую метод конечных элементов (МКЭ) для решения дифференциальных уравнений в частных производных.

У меня есть базовый класс Solver и два дочерних класса linSolver для линейного FEM и nlinSolver для нелинейного FEM. Члены и методы, которые используются обоими дочерними элементами, находятся в базовом классе. Все члены базового класса protected. Таким образом, использование наследования делает дочерние классы «простыми в использовании», как будто не было никакого наследования или других ограничений. Сам базовый класс, Solver, неполный, а это означает, что мне могут быть полезны только дочерние классы.

Концепция работает на самом деле очень хорошо, но я думаю, что непригодный для использования класс — это плохой дизайн. Кроме того, я читал, что наследование protected нежелательно и его следует по возможности избегать. Я думаю, что последний пункт на самом деле не относится к моему конкретному использованию, поскольку я никогда не буду использовать его, и любая попытка сделать это потерпит неудачу (поскольку он неполный).

Вопросы:

  • Распространено ли использование наследования для уменьшения двойного кода, даже если базовый класс будет непригоден для использования?
  • Каковы альтернативы или лучшие решения такой проблемы?
  • Действительно ли protected наследование плохо?

Спасибо за ваше время. Днаиэль


person dani    schedule 02.04.2014    source источник


Ответы (3)


Наличие «непригодных» базовых классов на самом деле очень распространено. У вас может быть базовый класс для определения общего интерфейса, используемого классами, наследующими базовый класс. И если вы объявите эти интерфейсные функции virtual, вы можете использовать, например. будут вызываться ссылки или указатели на базовый класс и правильная функция в унаследованном объекте класса.

Как это:

class Base
{
public:
    virtual ~Base() {}

    virtual void someFunction() = 0;  // Declares an abstract function
};

class ChildA : public Base
{
public:
    void someFunction() { /* implementation here */ }
};

class ChildB : public Base
{
public:
    void someFunction() { /* other implementation here */ }
};

С вышеуказанными классами вы можете сделать

Base* ptr1 = new ChildA;
Base* ptr2 = new ChildB;

ptr1->someFunction();  // Calls `ChildA::someFunction`
ptr2->someFunction();  // Calls `ChildB::someFunction`

Однако это не сработает:

Base baseObject;  // Compilation error! Base class is "unusable" by itself

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

void aGlobalFunction(Base* ptr)
{
    // Will call either `ChildA::someFunction` or `ChildB::someFunction`
    // depending on which pointer is passed as argument
    ptr->someFunction();
}

...

aGlobalFunction(ptr1);
aGlobalFunction(ptr2);

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

Конечно, базовый класс не обязательно должен быть полностью интерфейсным, он может содержать другие общие (protected) вспомогательные или служебные функции, которые можно использовать из всех классов, наследующих базовый класс. Помните, что наследование — это отношение «является» между классами. Если у вас есть два разных класса, которые оба "являются" чем-то, то использование наследования, вероятно, является очень хорошим решением.

person Some programmer dude    schedule 02.04.2014

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

virtual void f()=0;

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

person Silouane Gerin    schedule 02.04.2014

Не думайте о BaseClass как о самостоятельном классе, но как о контракте интерфейса и некоторой помощи в реализации. Следовательно, он должен быть абстрактным, если необходимо, объявив dtor чисто виртуальным, но в любом случае предоставив реализацию. Некоторые сторонники объектно-ориентированного программирования могут неодобрительно относиться к любому неприватному элементу, но чистота не является хорошей целью.

person Deduplicator    schedule 02.04.2014