LINUX.ORG.RU

C++: наследование с абстрактными классами и шаблонами

 


0

2

Есть базовый абстрактный класс MyBase. Есть его подвариант содержащий функции работающие с типом определяемым в шаблоне. MyBaseT<T>. В обоих классах есть pure-методы.

Далее есть реальный класс MyExtended наследующая MyBase и реализующий все недостающие pure-методы.

И есть класс MyExtendedT<T> наследующий MyExtended и MyBaseT<T> и реализующий pure-методы из MyBaseT<T>.

Нужно это все чтобы можно было наплодить MyExtendedT1<T> MyExtendedT2<T>MyExtendedTN<T> и со всеми ними работать как с MyBaseT<T> в общем случае.

Так же функции возвращающие значение T нужны(да и возможны) далеко не всегда, и тогда можно пользоваться вариантами MyExtended1, MyExtended2 .. MyExtendedN с которыми в общем случае можно работать как с MyBase.

Пытаюсь сделать я это примерно так

#include<stdio.h>

class MyBase
{
  public:
    virtual int SomeFunction() = 0; /*Not implemented at base class*/
};


template<class T> class MyBaseT: public MyBase
{
  public:
    virtual T TypeSpecificFunction() = 0; /*Not implemented at base class*/
};

class MyExtended: public MyBase
{
  public:
  virtual int SomeFunction() override;
};

int
MyExtended::SomeFunction()
{
  printf("MyExtended::SomeFunction\n");
  return 0;
}


template<class T> class MyExtendedT: public MyExtended, public MyBaseT<T>
{
  public:
    virtual T TypeSpecificFunction() override;
};


template<class T> T
MyExtendedT<T>::TypeSpecificFunction()
{
  printf("MyExtendedT<T>::TypeSpecificFunction\n");
  return 0;
}

int main()
{
    MyExtendedT<int> obj;
    obj.SomeFunction();
}

И у меня не получается. Оно почему-то считает, что получившийся MyExtendedT – абстрактный класс, и метод TypeSpecificFunction pure-метод

test.cpp: In function ‘int main()’:
test.cpp:46:22: error: cannot declare variable ‘obj’ to be of abstract type ‘MyExtendedT<int>’
     MyExtendedT<int> obj;
                      ^~~
test.cpp:30:25: note:   because the following virtual functions are pure within ‘MyExtendedT<int>’:
 template<class T> class MyExtendedT: public MyExtended, public MyBaseT<T>
                         ^~~~~~~~~~~
test.cpp:6:17: note:    ‘virtual int MyBase::SomeFunction()’
     virtual int SomeFunction() = 0; /*Not implemented at base class*/
                 ^~~~~~~~~~~~
test.cpp:47:9: error: request for member ‘SomeFunction’ is ambiguous
     obj.SomeFunction();
         ^~~~~~~~~~~~
test.cpp:6:17: note: candidates are: ‘virtual int MyBase::SomeFunction()’
     virtual int SomeFunction() = 0; /*Not implemented at base class*/
                 ^~~~~~~~~~~~
test.cpp:23:1: note:                 ‘virtual int MyExtended::SomeFunction()’
 MyExtended::SomeFunction()
 ^~~~~~~~~~
MyExtended::SomeFunction

Я что-то делаю не так? А как надо?

Или я хочу того что нельзя? Можно ли тогда добиться похожего эффекта другим способом?

★★★

Ответ на: комментарий от Silerus

Хм… а это нельзя?

Похоже что нельзя… Убрал ромбовидность,

template<class T> class MyBaseT/*: public MyBase*/

все заработало.

Хотя конечно остался вопрос, а почему нельзя… Но это я думаю я рано или поздно выясню…

Спасибо!!!

shaplov ★★★ ()
Ответ на: комментарий от slackwarrior

Можно, если ты понимаешь что делаешь и где поставить virtual :)

А вот где-бы об этом почитать, чтобы понимать, что я делаю :-)

Потому что пока я не очень… Ключевое слово virtual при наследываении, спасибо @fsb4000, я уже открыл. Но в чем фишка пока не понял… гуглю…

shaplov ★★★ ()

Другой вариант решения, который может быть удобнее виртуального наследования (зависит от сложности базового класса и как это всё используется):

template<class T>
class MyExtendedT: public MyExtended, public MyBaseT<T>
{
  public:
    virtual T TypeSpecificFunction() override;
    virtual int SomeFunction() override {
        return MyExtended::SomeFunction();
    }
};
xaizek ★★★★★ ()

Убрал ромбовидность,

template class MyBaseT /*: public MyBase */

все заработало.

А с какой целью это наследование было введено изначально? Если в MyBaseT есть какие-то другие методы, которым нужно обращаться к SomeFunction или к каким-то другим членам MyBase, то это решаемо без наследования от MyBase (например, сделать что-то вроде https://gcc.godbolt.org/z/YsdcTGbvq).

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

annulen ★★★★★ ()
Ответ на: комментарий от annulen

то это решаемо без наследования от MyBase

У меня есть обработчики общего случая которые работают с абстрактным классом BaseT<T> и зовут его TypeSpecificFunction (и на самом деле строят вектор из результатов TypeSpecificFunction, но это не важно)

В предложенном варианте такой номер не пройдет. Или в шаблон обработчиков надо будет тоже добавить template<class T, class Base> вместо template<class T> и код перестанет быть элегантным…

shaplov ★★★ ()
Ответ на: комментарий от shaplov

У меня есть обработчики общего случая которые работают с абстрактным классом BaseT и зовут его TypeSpecificFunction

Если никто не работает с BaseT как с Base, то надо вообще убрать наследование из BaseT<>. Если при этом нужен доступ к данным или методам Base, передать в конструктор BaseT<> указатель на Base и делать все через него. Да, это по сути виртуальное наследование вручную, но это все равно проще для понимания и не создает лишних граблей, на которые потом можно наступить.

annulen ★★★★★ ()
Ответ на: комментарий от annulen

Точнее, это замена наследования композицией (что почти всегда хорошо и правильно), только в этом случае наследование виртуальное, поэтому и композиция через указатель.

annulen ★★★★★ ()
Ограничение на отправку комментариев: только для зарегистрированных пользователей