4

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?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Charles
  • 953
  • 1
  • 8
  • 19
  • I think the word you're looking for is inheritance, not derivation? – Borgleader Aug 18 '13 at 00:12
  • What does this have to do with C? – Lightness Races in Orbit Aug 18 '13 at 00:20
  • 1
    Forget you ever knew Java, and get a [good C++ book](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). They are different. – GManNickG Aug 18 '13 at 00:25
  • 3
    Many assumptions that apply in Java will catastrophically fail in C++ (and vice-versa, of course). Pick up [a good introductory C++ book](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) and pretend you know nothing about Java when writing C++ code. – In silico Aug 18 '13 at 00:26
  • @Borgleader Stroustrup calls it derivation in his Principles, Programming & Practice book (is mentions its also called inheritance, but it seems like derivation was the more commonly used term from his presentation) – Charles Aug 18 '13 at 00:28
  • @GManNickG I'm trying, I have bought books and am getting slowly through them! – Charles Aug 18 '13 at 00:29
  • 1
    @SwanStomp: That's good. But you really have to stop thinking in Java while writing C++, which I suspect is the reason why you're struggling a bit. – In silico Aug 18 '13 at 00:31
  • 1
    I'm gonna guess that a Square may or may not have a Piece, hence it may need to be a POINTER to a Piece, and that pointer can be NULL. Pointers are something that many Java people do not know much about. I recommend learning about pointers vs. references vs. full object instances, along when to use each as data members of a class, along with ownership concerns (since there is no garbage collector in C++). Yeah, C++ is much different than Java. – franji1 Aug 18 '13 at 00:32

2 Answers2

6

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.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
5

This is all about "static polymorphism", aka method overloading. The particular version of operator<< is chosen by the compiler based on the compile-time type of the variables it sees; since get_piece() returns Piece, that's the version of operator<< that is chosen.

I have to point out that you are wrong about the equivalent Java program: Java method overloading is also arbitrated by the compiler, and is based on compile-time types. A truly equivalent Java program would display Piece, as well.

Ernest Friedman-Hill
  • 80,601
  • 10
  • 150
  • 186
  • Thanks! I was being silly and equating Java's .toString mechanism to overloading the << operator. D'oh! At least I get where I was going wrong now :-) – Charles Aug 18 '13 at 00:36