2

I would expect the following code to print out: "This is a Leg", because in my Lion class, it contains a private variable pointing to the Leg class.

#include <iostream>

using namespace std;

class Limb{
    public:
    
      std::string getFullName() const{
          auto name = getName();
          return "This is a " + name;
      }
      
      private:
          virtual std::string getName() const{
              return "Limb!";
          }
};

class Leg: public Limb {
    private:
      std::string getName() const override{
          return "Leg!"; //This is overriding the parent class's getName
      }
};

class Animal{
    public:
        Animal():x_(){}
        Limb getLimb() const{
            return x_; // This should return a Leg instance when running under `Lion`, right?
        }
        std::string getLimbName() const{
            return getLimb().getFullName();
        }
    private:
        Limb x_;
};

class Lion: public Animal{
    public:
        Lion():x_(){}
    private:
        Leg x_; //I explicitly want a Leg instance, not an Animal.
};

int main()
{
    Lion l = Lion();
    std::cout<<l.getLimbName()<<std::endl;
    return 0;
}
user1008636
  • 2,989
  • 11
  • 31
  • 45
  • 2
    `Limb x_;` -- `Leg x_;` -- The `x_` is not the same variable. They are two totally different entitites. If you thought that `x_` goes along for the ride when it comes to polymorphism, well, polymorphism doesn't work that way. – PaulMcKenzie Aug 27 '22 at 23:50
  • @PaulMcKenzie what can I do to make the above behave the way I want, ie print 'this is a Leg' and maintain same hierachy – user1008636 Aug 27 '22 at 23:51
  • @user1008636 Make Animal::getLimb() be virtual and returning a Limb& reference or Limb* pointer, then Lion can override getLimb() to return a reference/pointer to a Leg object. – Remy Lebeau Aug 27 '22 at 23:56
  • Ok, I understand why we have to make getLimb() a virtual function, but why do we need it to return a reference or pointer? @RemyLebeau – user1008636 Aug 27 '22 at 23:58
  • `Lion l = Lion();` -- This could simply be `Lion l;`. It looks like you're using another computer language as a model in writing C++ code. – PaulMcKenzie Aug 28 '22 at 00:07
  • @user1008636 if you don't, [the `Leg` object would get sliced](https://stackoverflow.com/questions/274626/). – Remy Lebeau Aug 29 '22 at 14:27

2 Answers2

1
std::cout<<l.getLimbName()<<std::endl;

This invokes this function:

        std::string getLimbName() const{
            return getLimb().getFullName();
        }

This invokes this function:

        Limb getLimb() const{
            return x_;
        }

This returns this object:

        Limb x_;

Which is a "Limb!".

 // This should return a Leg instance when running under `Lion`, right?

Nope. It is true that both Animal and Lion have a class member named x_. However that does not make them one and the same. C++ does not work this way. They are two completely separate objects that have absolutely nothing to do, whatsoever, with each other. The one in Lion is not involved in the above process, in any way.

Why doesn't this C++ child class end up running its parent's function?

Because it is not a child class, but the parent class, Limb. It is declared as such.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Thanks, what can I do to make it behave the way I want it to ? That is, the Lion (child class of Animal) should have a Leg and have that Leg share as much from Limb as possible ? – user1008636 Aug 27 '22 at 23:54
  • As declared, `Lion` does have a `Leg`, so it's unclear what you're asking. If you're asking how to have `getLimb()` return a `Leg`, it needs to be a virtual method that's overriden in `Lion`, that returns its `Leg`. Additionally it must return a reference, or a pointer to a `Limb`. It cannot return a `Limb`, just like that. Otherwise, well, it will return a `Limb` and nothing else. – Sam Varshavchik Aug 27 '22 at 23:56
  • Ok, I understand why we have to make getLimb() a virtual function, but why do we need it to return a reference or pointer? – user1008636 Aug 27 '22 at 23:58
  • Because that's how objects work in C++. Otherwise it will return an actual, real-life `Limb`, and not anything else. You seem to be under the impression that objects in C++ work like they do in Java or C#. C++ is not Java or C#, and in C++ if something returns an object, then that's the object that it will return. Full stop. An attempt to return a subclass will nuke the subclass from high orbit and the superclass. – Sam Varshavchik Aug 28 '22 at 00:00
  • But by making GetLimb() virtual, it'll call the child class (Lion)'s overridden version of GetLimb(), which will return a Leg, no? – user1008636 Aug 28 '22 at 00:01
  • No. It will attempt to return a Leg, slice it off, and return a Limb. Go ahead, try it, and see what happens. It would return a Leg, in this instance, in Java or C#, but C++ is not Java or C#. In fact, this is exactly how it's called in C++, "object slicing". Search Stackoverflow for this term. – Sam Varshavchik Aug 28 '22 at 00:01
  • @user1008636 -- One question -- are you using other computer languages, such as Java, to figure out how C++ works? If so, you have just run smack-dab into where doing such just leads to mistakes. In C++, there is something called object slicing, something that doesn't happen in those other languages. In C++, if you want an `X` you get an `X`, not a subclass of it (call it Y). Sure, the compiler will happily return a Y, but then the object gets sliced right back into an X. – PaulMcKenzie Aug 28 '22 at 00:04
  • @PaulMcKenzie Thanks, I am a python programmer, and I still don't get why returning a reference or pointer would make difference here. – user1008636 Aug 28 '22 at 00:09
  • Polymorphism only works with references to objects and pointers to objects. A reference or pointer cannot be sliced. – PaulMcKenzie Aug 28 '22 at 00:11
  • @PaulMcKenzie so in c++ if i want a function to be polymorphic, i have to make it return a reference or pointer. ? – user1008636 Aug 28 '22 at 00:19
  • A function is polymorphic by the virtue of it being declared `virtual` and overridden in the child class. That makes the function polymorphic. Object slicing semantics in C++ are something else, and have nothing to do with polymorphism. If you have a non-virtual function declaring as returning a `Limb`, but it attempts to return a `Leg`, you'll get the same results. If you have a function with a `Limb` parameter, and attempt to pass it a `Leg`, the function will get a `Limb` for its parameter. But if a function that takes a pointer or a reference to a `Limb`, passing it a `Leg` then... – Sam Varshavchik Aug 28 '22 at 00:25
  • @SamVarshavchik "A function is polymorphic by the virtue of it being declared virtual" <-- but you can override a base class's function in child class without the base class function declared virtual, right? – user1008636 Aug 28 '22 at 01:47
  • If by "override" you mean that invoking the method using the child class will invoke the child class method. But if th emthod gets invoked use the parent class, nothing gets overridden, unlike with a real virtual function. – Sam Varshavchik Aug 28 '22 at 01:59
  • @SamVarshavchik "But if th emthod gets invoked use the parent class, nothing gets overridden, unlike with a real virtual function." <-- why is this not a problem in python ? – user1008636 Aug 28 '22 at 21:33
  • Because Python is not C++, and C++ is not Python, they are two completely different languages, that work in fundamentally different ways. – Sam Varshavchik Aug 28 '22 at 21:58
1

As others have pointed out, Animal::x_ is a Limb. End of story. When an Animal looks for its x_ member it will find a Limb object because that's what you declared x_ to be. Unlike some other languages where a variable is implicitly a reference to some object, in C++ a variable is the object. Limb x can't refer to a Leg because it doesn't refer to anything. x is a Limb, and can never be anything else.

The fact that Lion also has a different member named x_ is irrelevant. Animal::x_ and Lion::x_ are different objects with no relation to one another.

If you want a derived class to be able to change what x_ is, then you'll need to give them a way to do so. The most common approach is a pattern called dependency injection. You add a constructor to your base class that accepts a pointer to the thing you want to inject, and derived classes create an appropriate object and pass a pointer to it to the base class's constructor:

class Animal {
public:
    Animal(std::unique_ptr<Limb> x) : x_{std::move(x)} {}

    const Limb& getLimb() const {
        return *x_;
    }

    std::string getLimbName() const {
        return getLimb().getFullName();
    }

private:
    std::unique_ptr<Limb> x_;
};

class Lion : public Animal {
public:
    Lion() : Animal{std::make_unique<Leg>()} {}
};

Demo


Note: With this change Limb also needs a virtual destructor since Legs will be deleted via pointers to Limbs.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52