Home > OS >  C Implementing a pure virtual function over a template
C Implementing a pure virtual function over a template

Time:08-17

I am trying to implement an abstract class (ElementHolder) over a Template (TElementHolder). The function virtual T* Fun2() seems to work fine but the function virtual void Fun(T* element); creates the following compile error:

source>:60:23: error: cannot declare variable 'fireHolder' to be of abstract type 'FireElementHolder'
   60 |     FireElementHolder fireHolder;
      |                       ^~~~~~~~~~
<source>:51:7: note:   because the following virtual functions are pure within 'FireElementHolder':
   51 | class FireElementHolder : public TElementHolder<Fire>
      |       ^~~~~~~~~~~~~~~~~
<source>:22:22: note:     'virtual void ElementHolder::Fun(Element*)'
   22 |         virtual void Fun(Element* element) = 0;
      | 

Can someone explain to me why the two functions create different results? And if I can make this work somehow?

I am not sure if Implementing a pure virtual function over a template is the correct way of saying what I am doing here.

source: https://godbolt.org/z/qEYdrYfYa

#include <iostream>

//------------------------------------------------------------------------------------------
class Element
{
    public:
        virtual ~Element() {}
        virtual void Fun() = 0;
};
class Fire : public Element
{
    public:
        ~Fire() {}
        void Fun() { std::cout << "Fire" << std::endl; }
};
//------------------------------------------------------------------------------------------
class ElementHolder
{
    public:
        virtual ~ElementHolder() {}

        virtual void Fun(Element* element) = 0;
        virtual Element* Fun2() = 0;
};

template<class T>
class TElementHolder : public ElementHolder
{
    public:
        virtual ~TElementHolder();

        virtual void Fun(T* element);
        virtual T* Fun2();
};
template<class T>
TElementHolder<T>::~TElementHolder() {}

template<class T>
void TElementHolder<T>::Fun(T* element)
{
    element->Fun();
}

template<class T>
T* TElementHolder<T>::Fun2()
{    
    std::cout << "Hello Fun2" << std::endl;
    return new T();
}

class FireElementHolder : public TElementHolder<Fire>
{

};
//------------------------------------------------------------------------------------------


int main()
{
    FireElementHolder fireHolder;
    auto instance = fireHolder.Fun2();
    fireHolder.Fun(instance);
    instance->Fun();
}

CodePudding user response:

Here is the working code:

#include <iostream>
//------------------------------------------------------------------------------------------
class Element {
    public:
        virtual ~Element() = default;
        virtual void Fun() = 0;
};

class Fire: public Element {
    public:
        ~Fire() = default;
        void Fun() { std::cout << "Fire" << std::endl; }
};
//------------------------------------------------------------------------------------------
class ElementHolder {
    public:
        virtual ~ElementHolder() = default;
        virtual void Fun(Element* element) = 0;
        virtual Element* Fun2() = 0;
};

template<class T>
class TElementHolder: public ElementHolder {
    public:
        virtual ~TElementHolder() = default;
        virtual void Fun(Element* element) {
            element->Fun();
        }
        virtual T* Fun2() override {
            std::cout << "Hello Fun2" << std::endl;
            return new T();
        }
};

class FireElementHolder: public TElementHolder<Fire> {};
//------------------------------------------------------------------------------------------
int main() {
    FireElementHolder fireHolder;
    auto instance = fireHolder.Fun2();
    fireHolder.Fun(instance);
    instance->Fun();
}

Some highlights:

  1. The problem was in the signature of

virtual void Fun(T* element)

which had to be changed to

virtual void Fun(Element* element)

  1. You were missing some virtuals in some places that were inserted.

CodePudding user response:

There is a well-known problem with your design.

You have two parallel class hierarchies, Elements and ElelementHolders. You want each concrete ElementHolder to accept a pointer or reference to a corresponding Element. So you add a pure virtual function to the base class

class ElementHolder {
   virtual void Fun(Element* element) = 0;

and override it in each derived class (I de-templatized your hierarchy for ease of exposition, there is a T instead of Fire in your code, but it doesn't really affect anything):

class FireHolder : public ElementHolder {
   virtual void Fun(Fire* fire) { ... }

Which is a very natural and sensible choice.

Except that it doesn't work at all, because void Fun(Fire* fire) doesn't override void Fun(Element* element). They are completely independent functions.

Now how do you solve this problem? There are two very different ways.

  1. Get rid of ElementHolder::Fun altogether.
  2. Change void Fun(Fire* fire) to void Fun(Element* fire). Cast fire to Fire* inside.

How does one cope without ElementHolder::Fun? Simply, just don't call it ;) You cannot safely use it anyway. The signature Fun(Element*) says that any element can be passed. In fact you can only pass Fire* to a FireHolder, and any other element is an error (so the signature lies---not a good idea). If you only have an ElementHolder and an Element *, there is no guarantee you can call Fun.

If you absolutely must call ElementHolder::Fun, then #2 is your only option.

  • Related