简体   繁体   中英

Calling subclass (virtual) function (late binding), when not dealing with pointers

Looking at the answer Why do we need virtual functions in C++ , they show that virtual functions allow late binding, which results in being able to call subclass functions, when being downcasted.

For completness, I include the code in question.

class Animal
{
 public:
   virtual void eat() { 
       std::cout << "I'm eating generic food.";
   }
};
class Cat : public Animal
{
public:
  void eat() { std::cout << "I'm eating a rat."; }
};

Now consider the following two function

void func(Animal *xyz) { xyz->eat(); }
void func2(Animal xyz) { xyz.eat(); }

We see that calling func results in

Animal *animal = new Animal;
Cat *cat = new Cat;
func(animal); // outputs: "I'm eating generic food."
func(cat);    // outputs: "I'm eating a rat."

While calling func2 results in

Animal animal;
Cat cat;
func2(animal); // outputs: "I'm eating generic food."
func2(cat);    // outputs: "I'm eating generic food."

My question is:

How can I make it so, that functions receiving arguments, that are not pointers, to instances of subclasses, will use the overriden methods? In other words, how can func2 result in "I'm eating a rat.". Further, I would like to understand why this difference arises. I thank you in advance.

How can I make it so, that functions receiving arguments, that are not pointers, to instances of subclasses, will use the overriden methods? In other words, how can func2 result in "I'm eating a rat.".

You can use references instead of pointers.

void func2(Animal& xyz) { xyz.eat(); }

Then, using

Animal animal;
Cat cat;
func2(animal);
func2(cat); 

will work as you are hoping.

Further, I would like to understand why this difference arises.

That is caused by object slicing. See What is object slicing? to understand what object slicing is and how that comes into play in your code.

The whole virtual function mechanism in C++ is based on the idea of selecting the actual function to call based on the dynamic ("actual") type of the object used in that call. In this function

void func2(Animal xyz) { xyz.eat(); }

the type of xyz object is Animal . It is explicitly hardcoded to be Animal . Its static type is Animal . Its dynamic type is Animal . It is Animal in all respects. You yourself asked the compiler to make it so by using the above parameter declaration.

Which means that xyz.eat(); call will always call Animal::eat() . There's no way around it. There's no way to make it call Cat::eat() since there's no Cat object involved here. By passing a Cat as an actual argument, you are simply asking the compiler to generate an Animal xyz from that Cat and then ignore the original Cat . (This is what is often referred to as "slicing".) All subsequent work is done with Animal xyz .

The explanation is the func2 function does not work with the object you pass to it. The function has its own argument variable Animal xyx which is constructed from your animal or cat variable. So inside the func2 you always have a base-class object to work on, hence the function always invokes the generic answer.

void func2(Animal xyz)  // uses its own Animal xyz, created on call
{
    xyz.eat();          // uses a local xyz object of Animal class with its generic functions
}

Cat cat;
func2(cat);       // here a new Animal xyz is created from the cat

You must pass either a pointer or a reference to your actual object as a parameter to the function to allow the function access the object's specific overridden virtual functions. Like this:

void func2(Animal& xyz)   // uses a reference to the argument object
{
    xyz.eat();            // the actual argument is used with its overridden functions
}

Cat cat;
func2(cat);               // the cat is passed to the callee

You can implement your classes using value semantics. Then you don't need pointers or references. See " Better Code: Runtime Polymorphism " by Sean Parent, which may just blow your mind. Here's the code . Watch the talk and see how he uses value semantics to avoid bad data sharing and implement an impressive, polymorphic undo system. Here's an annotated sample:

// A generic draw function that any type that streams to std::ostream can use
template <typename T>
void draw(const T& x, ostream& out, size_t position)
{ 
  out << string(position, ' ') << x << endl; 
}

// A class that can hold anything with value semantics
// -- note: no virtual functions and no inheritance!
class object_t {
    // ... see the talk for the details here ...
    // This is where the magic happens
};

// Define a vector of our object_t to be a document
using document_t = vector<object_t>;

// Overload the draw() function for document - just iterate
// through the vector and call draw() on each item in it
void draw(const document_t& x, ostream& out, size_t position)
{
    out << string(position, ' ') << "<document>" << endl;
    for (auto& e : x) draw(e, out, position + 2);
    out << string(position, ' ') << "</document>" << endl;
}

// Define my own class that the code above knows nothing 
// about and that doesn't inherit from object_t
class my_class_t {
    /* ... */
};

// Overload draw for it
void draw(const my_class_t&, ostream& out, size_t position)
{ out << string(position, ' ') << "my_class_t" << endl; }

// Use all this stuff
int main()
{
    document_t document; // just a vector!

    // Add some objects that don't inherit from object_t!
    document.emplace_back(0);
    document.emplace_back(string("Hello!"));

    // Show what we've got so far
    draw(document, cout, 0);

    // Add some more stuff
    document.emplace_back(document); // Add a whole copy of the current doc!
    document.emplace_back(my_class_t()); // Add an arbitrary type that doesn't inherit from object_t!

    draw(document, cout, 0);
}

This prints:

<document>
  0
  Hello!
</document>
<document>
  0
  Hello!
  <document>
    0
    Hello!
  </document>
  my_class_t
</document>

See it run on Wandbox .

Look again -- you just got polymorphic behavior without having to explicitly inherit from a base class. (Hint: he uses overloading as the primary mechanism rather than inheritance.)

See also this blog post describing this approach.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM