Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble understanding C++ derivation

Tags:

c++

I'm bringing a lot of assumptions from Java over to my learning of C++ which seems to have stumped me again. I don't have the vocabulary to eloquently say what I expect to see from the following program, so I'll just present it and say what I expected to see:

#include <iostream>
#include <vector>

using namespace std;

class Piece {};

class Man : public Piece {};

class Square {
  Piece p;
public:
  Square(Piece p) : p(p) { };
  Piece get_piece() const { return p; }
};

ostream& operator<<(ostream& os, const Piece& p)
{
  cout << "Piece";
  return os;
}

ostream& operator<<(ostream& os, const Man& m)
{
  cout << "Man";
  return os;
}

ostream& operator<<(ostream& os, const Square& s)
{
  cout << s.get_piece() << '\n';
  return os;
}

int main()
{
  Square sq = Square(Man());
  cout << sq;
}

When I run this program the output is Piece, but I expected to see Man. Is this called run-time polymorphism? I thought that was reserved to functions, but I don't know. The "equivalent" program in Java prints what I expect, Man, but I don't know how to get this C++ program to do that. What am I missing?

like image 451
Charles Avatar asked Dec 20 '22 01:12

Charles


1 Answers

Unlike Java C++ distinguishes between references to objects and values, i.e., the objects themselves. When you pass objects of a derived type to a function taking a value of the base type you get a sliced object: it will only contain a copy of the base part of the object and nothing of the derived type. For example, your constructor

Square(Piece piece)

takes its argument by value and it will always be of type Piece and never of any derived type: the argument, if it was of a derived type, got sliced. You can pass objects by reference using a notation like

Square(Piece& piece)

if the object referenced by piece is mutable or

Square(Piece const& piece)

if the object referenced by piece should be immutable. In your context you most likely also want to deal with the life-time management of your objects which is probably best done using objects allocated on the heap using new and maintained by some smart pointer, e.g., std::shared_ptr`.

Now on to your output functions: the function being called is always statically resolved based on the static type, i.e., the type declared and visible during compilation. Once the correct function is called, if it happens to be declared virtual, it dispatches to a possible overriding function based on the dynamic type of the object, i.e., the virtual dispatch is done at run-time. For the purpose of you output operators this means they get only chosen based on the static type, which in your case is always Piece. The usual way to deal with that is to use a virtual function and dispatch to this function from the actual output operator, e.g.:

class Piece {
protected:
    virtual std::ostream& do_print(std::ostream& out) const  = 0;
public:
    std::ostream& print(std::ostream& out) const { return this->do_print(out); }
};
std::ostream& operator<< (std::ostream& out, Piece const& piece) {
    return piece.print(out);
}

class Man: public Piece {
protected:
    std::ostream& do_print(std::ostream& out) {
        return out << "Man";  // note: you want to use out, not std::cout here
    }
};

With this setup you can call the static output operator Piece using a reference to an object of this type and get the output chosen by the dynamic type, e.g.:

class Square {
    std::shared_ptr<Piece> d_piece;
public:
    Square(std::shared_ptr<Piece> piece): d_piece(piece) {}
    Piece const& get_piece() const { return *this->d_piece; }
};
std::ostream& operator<< (std::ostream& out, Square const& square) {
    return out << square.get_piece();
}

The somewhat quirky forward of print() to do_print() isn't really required but if you have multiple virtual overloads with the same name and you override just one of them, all the other versions in the base class get hidden. Since print() isn't being overridden and do_print() isn't being called by the user, the problem of hiding overloads is somewhat reduced.

like image 185
Dietmar Kühl Avatar answered Jan 15 '23 20:01

Dietmar Kühl