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:
- The problem was in the signature of
virtual void Fun(T* element)
which had to be changed to
virtual void Fun(Element* element)
- 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, Element
s and ElelementHolder
s. 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.
- Get rid of
ElementHolder::Fun
altogether. - Change
void Fun(Fire* fire)
tovoid Fun(Element* fire)
. Castfire
toFire*
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.