15

First, I'm Java coder and want to understand polymorphism in c++. I wrote the example for learning purposes:

#include<iostream>

using namespace std;

class A
{
public:
    virtual void foo(){ std::cout << "foo" << std::endl; }
};

class B : public A
{
public:
    void foo(){ std::cout << "overriden foo" << std::endl; }
};

A c = B(); 

int main(){ c.foo(); } //prints foo, not overriden foo

I expected that overriden foo would be printed, but it wasn't. Why? We overrode the method foo in the class B and I thought that the decision which method should be called is being making from the runtime type of the object which in my case is B, but not a static type (A in my case).

Live example is there

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
stella
  • 2,546
  • 2
  • 18
  • 33
  • [Please do not use `using namespace std;`.](http://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice) – orlp Mar 15 '15 at 11:14
  • You have to access `foo()` through a `A*` pointer or `A&` reference. – πάντα ῥεῖ Mar 15 '15 at 11:14
  • 1
    @πάνταῥεῖ Couldn't you clarify you comment a bit? Ok, it works, but why is there so difference? I can't explain that for myself in a logical way... – stella Mar 15 '15 at 11:17
  • 1
    The confusion is a result of the fact that Java uses pointers all over the place but tries to hide it. In Java `A c = new B();`, the variable `c` is actually a pointer that references a `B` object. The equivalent (ignoring deallocation) in C++ is `A *c = new B();`. To understand why `A c` in C++ can't work the same as `A c` in Java, consider the memory layout and what happens when `B` objects are twice the size of `A` objects. – bames53 Mar 15 '15 at 11:43
  • @bames53 No, I understand what does `A c` mean in c++ and what're the difference from java. As far as I know it causes default object initialization (calling default constructor), if any. My question now is how implicit copy constructor produces the object of type A here. – stella Mar 15 '15 at 11:47
  • Consider a function `void foo(A &a);`. If `foo` is a function that's intended to use the polymorphic interface declared by `A` in order to activate functionality in `A` subclasses then obviously this function needs to be able to take a `B` object as an argument. By default, the conversion produces a reference to the `A` subobject inside the `B` object (again, think about the memory layout produced by `class B : public A, public C, public D {}`). – bames53 Mar 15 '15 at 11:57
  • `A`'s copy constructor takes a reference too, and so the same conversion works. Only the copy constructor isn't about operating the polymorphic interface, it's about copying things. The copy-ctor only knows it has a reference to an `A` object, and so it dutifully copies the `A` subobject out of of any `B` passed to it. This is called 'slicing'. – bames53 Mar 15 '15 at 11:57

5 Answers5

21

When you do this:

A c = B(); 

You're converting the B value into A. You don't want that.

You should make a B object and access it through an A pointer or reference to get polymorphic behaviour:

B b;
A& c = b;
orlp
  • 112,504
  • 36
  • 218
  • 315
  • Did you mean that I just called the copy constructor which produces the object of the runtime type `A`? – stella Mar 15 '15 at 11:24
  • 1
    @stella You caused implicit conversion of B -> A when doing `A c = B();` . – orlp Mar 15 '15 at 11:26
  • Honestly, I dig into c++11 standard chapter 4. I'd say, that it's inside the implementation of the copy constructor, that does some stuff and produces the object of the runtime type A. I'm not sure about that, but why not? – stella Mar 15 '15 at 11:42
  • 1
    @stella I'm sorry, I don't understand your question. – orlp Mar 15 '15 at 11:50
  • @stella Yes, that's basically what's happening. `A::A(A const&)` is called, with the temporary `B` you've constructed (which, of course, can be used by that constructor, because it's an `A`). Afterwards, the temporary `B` is discarded, and `c` (the copy of the temporary's `A`-part) is kept around. – Sneftel Mar 16 '15 at 09:19
14

In java, you have value semantics with types like int and float, and you have reference semantics with everything else.

That's not the case in C++: the type system is unified, and you get whichever of value or reference semantics that you ask for.

With the code you've written

A c = B()

you've told the compiler to create a new value of type B, and then convert it to a value of type A, storing the value in c. Conversion, in this case, means taking A data out of the new B instance you created it, and copying it into the new A instance stored in c.

You could do this instead:

B b;
A &c = b;

This still creates the value b, but now c is a reference to A, which means c will now refer to the B instance you created, rather than being a copy of its A part.

Now, this still creates b as a local variable, and the object stored in b gets destroyed as soon as b goes out of scope. If you wanted something more persistent, you'd need to use pointers; e.g. something like

shared_ptr<A> c = make_shared<B>();
c->foo();

You could do something more 'raw' like

A *b = new B();

but this is a 'dumb' pointer; shared_ptr is smarter and your object will get destroyed when nothing else references it. If you do the latter, you'd have to do the destruction yourself when appropriate (and messing this up is a common source of mistakes)

  • C++'s type system is definitely not unified and value/reference semantics are orthogonal to the issue. – Voo Mar 15 '15 at 17:13
  • 1
    @Voo: True, but it's moreso than java. I read the OP as being confused, expecting `A c` to declare a variable with 'reference to `A`' semantics as it would be in java; I suppose it could read it differently, although I don't see it. –  Mar 15 '15 at 17:39
  • In Java everything but primitives has one parent type `Object`, sure not complete, but C++ doesn't even have anything like that. I think you misunderstand what a "unified typesystem" is (e.g. Scala has a real unified type system with `Any`). C++ doesn't have one and doesn't want one and there are legitimate reasons for why they don't. – Voo Mar 15 '15 at 20:03
  • 1
    @Voo: Ah, the perils of natural language vs. technical language. :( I did not mean "unified" in the technical sense, but in the sense that there isn't a sharp divide between primitives, other objects, functions, and such have a very different set of behaviors. –  Mar 15 '15 at 21:06
7

Your confusion stems from a crucial difference between Java and C++.

In Java if you write

MyClass var = whatever;

your variable var is a reference to the object returned by whatever. However, in C++ this syntax means "create a new object of type MyClass by passing the result of the expression whatever to an appropriate constructor, and copy the resulting object into the variable var.

In particular, your code creates a new object of type A, named c, and passes a temporary default-constructed object of type B to its copy constructor (because that's the only constructor that fits). Since the newly created object is of type A, not of type B, obviously A's method foo is called.

If you want to have a reference to an object, you have to explicitly request that in C++, by adding & to the type. However a reference to non-constant objects cannot be bound to temporaries. therefore you need to explicitly declare also the object you bind to (or alternatively, use a reference to a const object, and fix your foo member functions to be const, since they don't change the object anyway). So the simplest version of your code doing what you want would read:

// your original definitions of A and B assumed here

B b; // The object of type B
A& c = b; // c is now a *reference* to b
int main() { c.foo(); } // calls B::foo() thanks to polymorphism

However the better version would be const-correct, and then could use your original construction:

#include <iostream>

class A
{
public:
    virtual void foo() const  // note the additional const here!
    { std::cout << "foo" << std::endl; }
};

class B : public A
{
public:
    void foo() const // and also const here
    { std::cout << "overridden foo" << std::endl; }
};

A const& c = B(); // Since we bind to a const reference,
                  // the lifetime of the temporary is extended to the
                  // lifetime of the reference

int main() { c.foo(); } //prints overridden foo

(note that I removed using namespace std; because it's a bad thing to do (and your code used explicit std:: anyway, so it's just redundant).

Note however, that C++ references are still different from Java references in that they cannot be reassigned; any assignment goes to the underlying object instead. For example:

#include <iostream>

class A { public: virtual void foo() const { std::cout << "I'm an A\n"; } };
class B: public A { public: void foo() const { std::cout << "I'm a B\n"; } };
class C: public A { public: void foo() const { std::cout << "I'm a C\n"; } };

B b;
C c;

int main()
{
   A& ref = b; // bind reference ref to object b
   ref.foo(); // outputs "I'm a B"
   ref = c; // does *not* re-bind the reference to c, but calls A::operator= (which in this case is a no-op)
   ref.foo(); // again outputs "I'm a B"
}

If you want to change the object you refer to, you'll have to use pointers:

// definitions of A, B and C as above
int main()
{
  A* prt = &b; // pointer ptr points to b
  prt->foo();  // outputs "I'm a B"
  prt = &c;    // reassign ptr to point to c
  prt->foo();  // outputs "I'm a C"
}
celtschk
  • 19,311
  • 3
  • 39
  • 64
4

The line of interest is this (using uniform initialization syntax instead):

A c = B{};

It is important to note that, when declared this way, c behaves like a value.

Your code constructs a local A named c out of an instance of B. This is called slicing: Any part of that B that isn't also part of an A has been "sliced" away, leaving only an A.

Giving a symbol reference semantics in C++ (called indirection) requires a different notation.

For example:

A &c = B{};
A d = B{}; // Casts the intermediate B instance to const A &,
           // then copy-constructs an A

c.foo(); // calls B::foo because c points to a B through an A interface

d.foo(); // calls A::foo because d is only an instance of an A

Note that the lifetime of the intermediate B to which c points is automatically extended to the scope of c. On the other hand, the second intermediate B is destroyed after the construction of d has completed.

In C++, references are immutable (they cannot be changed after initialization). When used in an expression, it is as though the object (value) to which they are pointing were used instead:

A &c = B{};

c = A{}; // Calls the compiler-provided A::operator = (const A &)
         // (a virtual assignment operator with this signature
         // was not defined).
         // This DOES NOT change where c points.

Pointers, on the other hand, can be changed:

A a{};
B b{};

A *cptr = &b;

cptr->foo(); // calls B::foo

cptr = &a;

cptr->foo(); // calls A::foo
defube
  • 2,395
  • 1
  • 22
  • 34
0

exactly what orlp said. you should also learn to use pointers too (they are fun)

A* c = new B();
c->foo(); // overriden foo
delete c;
Emil Laine
  • 41,598
  • 9
  • 101
  • 157
iNyuu
  • 62
  • 6