3

So I have a problem to override a pure virtual function in my derived class. The implementation and declaration of the classes looks like this:

class Base{
private:
   size_t id;
public:
   virtual bool isEqual(const Base& src) const =0;
};

class Derived: public Base{
private:
    string str;
public:
    virtual bool isEqual(const Derived& src) const override
    {
        return (this->str == src.str);
    }
};

so when I implement it like this it hits me with compiler error like

member function declared with 'override' does not override a base class member function

Could you plz tell me how I could get that right and maybe explain me why my version doesn't work. Thanks in advance!

  • Because the arguments are different you're not overriding the base-class function. To override a function you need to specify the exact same signature. – Some programmer dude Dec 18 '19 at 09:17
  • I understand that the arguments are different, but I need to get them different since base class does not contain the ```string str ``` data member in it and the compiler would throw an error for that specifically in my isEqual overriden member function...So I guess I should just overload the function and delete the ``=0`` part that makes it a pure virtual function ?? – Vissarion Moutafis Dec 18 '19 at 09:26
  • 4
    @VissarionMoutafis Wrong, a `Derived` **is** a `Base`. So you can override it with the same signature (taking a `const Base &`) and passing a `Derived` object will work fine. In the function body, you can `dynamic_cast`. If it fails, you can throw an exception or anything you want (bad type given, `Derived` expected, ...). – Fareanor Dec 18 '19 at 09:32
  • ... or in given case simply return false: If types do not match, the objects cannot be equal either. – Aconcagua Dec 18 '19 at 09:35
  • @Aconcagua Yes, it is even better :) – Fareanor Dec 18 '19 at 09:38
  • By the way, you even *need* to accept reference to base for yet another reason: `Derived d1, d2; Base& b1 = d1; Base& b2 = d2; b1.isEqual(b2);` – how would this work otherwise? – Aconcagua Dec 18 '19 at 09:40
  • Wait wait, let's say that I have 2 Derived Class objs with the same string and I want: 'if(obj1.isEqual(obj2)) cout << "Equal" < – Vissarion Moutafis Dec 18 '19 at 09:41
  • 1
    @VissarionMoutafis You *only* return false if the `dynamic_cast` fails. If so, you have different object types and they *cannot* be equal. *Otherwise* you return the result of the string comparison as you intended already now. – Aconcagua Dec 18 '19 at 09:44
  • By the way, why don't you call your function `operator==`??? – Aconcagua Dec 18 '19 at 09:48
  • The teacher told us that we must not overload operator since it's in the next part of the assignment and focus on the pure virtual function implementation and later to the overloaded operators.....yeah I know – Vissarion Moutafis Dec 18 '19 at 09:58

2 Answers2

8

You cannot change the function signature that way – read about co- and contravariance for details and why C++ disallows both for function parameters (covariant return types are allowed).

On the other hand, if the other object is referred to via reference to base, the overriding (actually: overloading!) function would not be called at all:

Derived d1;
Derived d2;
Base& b2 = d2;

d1.isEqual(b2); // how do you imagine the derived version to get called now???

Key to a solution is a dynamic_cast:

struct Base
{
    virtual ~Base() { }
    virtual bool operator==(Base const& other) = 0;
};

struct Derived : Base
{
    bool operator==(Base const& other) override
    {
        auto o = dynamic_cast<Derived const*>(&other);
        return o && o->str == this->str;
    }
};

Note that I renamed the function to operator==; this is far more natural in C++. With that, you can compare in the example above with:

bool isEqual = d1 == b2;

Edit:

The teacher told us that we must not overload operator

Well, then just revert the renaming... Actually, operators are just as ordinary functions as any other one else, solely that calling syntax differs (actually: an alternative variant exists, you could always call as d1.operator ==(d2), too).

walnut
  • 21,629
  • 4
  • 23
  • 59
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • 2
    "The teacher told us that we must not overload operator" - actually the best approach is an external function `bool operater ==(const Base& rhs, const Base& lhs) { return rhs.isEqual(lhs); }` - this works much betters when there is a conversion operator from something else to a Base or Derived object. – Martin Bonner supports Monica Dec 18 '19 at 10:20
  • Wow you figured this one out. I'm very surprised that you cannot fulfill a virtual function with a signature using derived classes. A derived class is a superset, so it should be able to be used as its base class anytime. And in c++ it works that way for everything else. Up-casting a base class to a derived class never feels right. – Joe C Feb 08 '21 at 04:19
  • @JoeC To be precise: The instances of derived classes form a **subset** of the instances of the base class! Imagine a base class `Cat` with derived classes `Lion`, `Tiger` and `Cheetah`. All derived classes are `Cat`s as well, but not all `Cat`s are `Tiger`s. If now another base class accepts `Cat`s, but the derived one only `Tiger`s, the base could accept `Lion`s, the derived one not, though. Thus the interface to implement remains incomplete. *Returning* a derived type instead of a base type (covariant return value) is type-safe (but object slicing might occur if you return by value). – Aconcagua Feb 09 '21 at 08:50
  • @JoeC To accept parameters, we might use a more generic type in the sub-class, e. g. accept a `Tiger` in base, but a `Cat` in derived (contravariant parameters). However we can use function arguments as additional return values (via pointers/references)! If now base requires to place the address of a `Tiger` instance into the `Tiger` pointer, but derived accepting a `Cat` pointer places a `Cheetah` instead we are again in trouble. We'd need a covariant parameter now! As compiler cannot decide (used as in or out parameter or both), variant parameters are not allowed at all in C++. – Aconcagua Feb 09 '21 at 08:50
3

The principle of polymorphism is that a Derived is a Base. If you want to override a function, the signature must be the same.

The proper way to solve your issue is to define your override as something equivalent to:

bool Derived::isEqual(const Base & src) const
{
    try
    {
        Derived & d = dynamic_cast<Derived &>(src);
        return (this->str == d.str);
    }
    catch(const std::bad_cast & e)
    {
        // src is not a Derived
        return false;
    }
}

If you want to work with pointers, you can do the same as follows:

bool Derived::isEqual(const Base * src) const
{
    const Derived * d = dynamic_cast<const Derived*>(src);

    if(d == nullptr) // src is not a Derived*
        return false;
    else
        return (this->str == d->str);
}

Of course it assumes that you have a matching definition in Base to override.


The solution provided by @Aconcagua uses the same idea in a more elegant way. I would advise to use his solution instead.

Fareanor
  • 5,900
  • 2
  • 11
  • 37
  • In case we have another Derived class like `Derived2` and in case you pass an object of `Derived2`, program crashs! I think its better to have a dirty solution than providing user the possibility of crash – rezaebrh Dec 18 '19 at 10:00
  • 1
    @rezaebrh It does not. Why do you think that? – walnut Dec 18 '19 at 10:01
  • Have you used that already? It wont return false in all of the cases! – rezaebrh Dec 18 '19 at 10:05
  • @Fareanor I do recommand have a look at https://stackoverflow.com/questions/278429/what-could-cause-a-dynamic-cast-to-crash since you are the most professional person at polymorphism! – rezaebrh Dec 18 '19 at 10:07
  • @Fareanor I'm not the one who downvoted but I think you are! – rezaebrh Dec 18 '19 at 10:08
  • 1
    @rezaebrh In the link you provided there's a crash because of missing null-pointer check. Such one is applied in this answer, so everything is fine. – Aconcagua Dec 18 '19 at 10:15
  • 2
    @rezaebrh These are orthogonal problems one has to address separately, e. g. correct thread synchronisation. But that goes beyond the scope of this question... – Aconcagua Dec 18 '19 at 10:30
  • As my comments have been deleted (I do not understand why), here is a summary: This solution will not crash, even if a `Derived2` object is given (assuming it inherits `Base` too, it will not compile otherwise), because `Derived2` would be a `Base` too. This is how polymorphism works. The given link in comments is irrelevant here since my `dynamic_cast` is properly checked. – Fareanor Dec 18 '19 at 15:49