6
#include <cstdio>
using namespace std;

class A {
public:
    virtual void func() { printf("A::func()"); }
};

class B : public A {
public:
    virtual void func() { printf("B::func()"); }
};

int main() {
  A a = *(A *)new B();
  a.func();
}

The question is simple: why a->func() calls function in class A even though a contains object of class B?

Venemo
  • 18,515
  • 13
  • 84
  • 125
mnn
  • 1,952
  • 4
  • 28
  • 51
  • When the syntax errors are fixed so that it compiles, your code does do the right thing (calls `B::func()`). As this can't be your *actual* code, I have no idea what your problem might be! – Oliver Charlesworth Jan 01 '11 at 14:22
  • This code does compile (at least for me). Visual C++ 2008 Express – mnn Jan 01 '11 at 14:23
  • @mnm: Ah, I see you've changed it. Actually, it still doesn't compile (missing `;` at the end of each class definition). – Oliver Charlesworth Jan 01 '11 at 14:24
  • OK, sorry for those. Still no go, as a->func() calls A::func() – mnn Jan 01 '11 at 14:26
  • When you get that to a point where it compiles, you should look at this question that's almost certainly a dupe: http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c – bgporter Jan 01 '11 at 14:27
  • That code still contains errors (there is no `operator->` for class `A`), please post a valid example. – Philipp Jan 01 '11 at 14:27
  • 1
    also, a is an object instance, so you cannot use the -> pointer notation. And you should consider automatic pointers. on top of that, the printf() function is C, not C++. – BatchyX Jan 01 '11 at 14:28
  • Philipp, it just works, I don't care. BatchyX: Yes, I know printf() is C function, but that was just an example. – mnn Jan 01 '11 at 14:34
  • 1
    Sorry, it can't work. a is not a pointer type, and you have not redefined it's -> operator, so you cannot use -> on it. – BatchyX Jan 01 '11 at 14:36
  • BatchyX: Sorry, I missed that one too :( – mnn Jan 01 '11 at 14:38
  • I edited your code so that it actually works. `main` function, includes, and `using namespace` were still missing. – Philipp Jan 01 '11 at 14:55
  • Philipp, yes all of that was missing, but not essential to the issue itself. – mnn Jan 01 '11 at 15:33

7 Answers7

14
A a = *(A *)new B();
a.func();

Here's what happens in this code, step by step:

  • new B(): a new object of type B is allocated on the free store, resulting in its address
  • (A*): the address of the object is cast to A*, so we have a pointer of type A* actually pointing to an object of type B, which is valid. All OK.
  • A a: here the problems start. A new local object of type A is created on the stack and constructed using the copy constructor A::A(const A&), with the first paremeter being the object created before.
  • The pointer to the original object of type B is lost after this statement, resulting in a memory leak, since it was allocated on the free store with new.
  • a.func() - the method is called on the (local) object of class A.

If you change the code to:

A& a = *( A*) new B();
a.func();

then only one object will be constructed, its pointer will be converted to pointer of type A*, then dereferenced and a new reference will be initialized with this address. The call of the virtual function will then be dynamically resolved to B::func().


But remember, that you'd still need to free the object since it was allocated with new:

delete &a;

Which, by the way, will only be correct if A has a virtual destructor, which is required that B::~B() (which luckily is empty here, but it doesn't need to in the general case) will also be called. If A doesn't have a virtual destructor, then you'd need to free it by:

delete (B*)&a;

If you would want to use a pointer, then that's the same as with the reference. Code:

A* a = new B(); // actually you don't need an explicit cast here.
a->func();
delete (B*)a; // or just delete a; if A has a virtual destructor.
Kos
  • 70,399
  • 25
  • 169
  • 233
  • +1 for the very nicely explained answer. :) Just a side comment: many people prefer to refer to the "free store" as "heap". – Venemo Jan 01 '11 at 15:21
  • @Kos: I would underline that `delete &a;`, `delete (B*)&a;` as well as `delete (B*)a;` are just tricks to make it work, while one should really avoid this in practice and go with pointers and virtual destructors. – Roman L Jan 01 '11 at 15:34
  • @Venemo, see http://stackoverflow.com/questions/1350819/c-free-store-vs-heap - I'm not sure, but 'free store' seems to be more "C++ standard terms" and probably is correct :) – Kos Jan 01 '11 at 15:46
  • @7vies, True! I've indeed focused on "how to make it technically correct", rather than "how to make it look nice"... but then, good practices of memory management, smart pointers, etc in C++ is kind of a broad topic, so I just refrained to recommending a virtual destructor... oh, wait, did I? I hereby recommend a virtual destructor! :) – Kos Jan 01 '11 at 15:48
  • @Kos: it's not about covering a broader topic, you just didn't make it clear that your technically correct tricks shouldn't actually be used. Like "this would work, but you should never ever do this but do *that* instead". Otherwise some people can learn bad things from you. – Roman L Jan 01 '11 at 16:05
5

The problem you encounter is classic object slicing :

A a = *(A *)new B();

Make a either a reference or pointer to A, and virtual dispatch will work as you expect. See this other question for more explanations.


You commented on another answer that "Compiler should at least give warning or what". This is why is it considered a good practice to make base classes either abstract of non copyable : your initial code wouldn't have compiled in the first place.

Community
  • 1
  • 1
icecrime
  • 74,451
  • 13
  • 99
  • 111
  • Nice link, but I'm afraid this problem is totally unrelated to "slicing". The problem here is that the OP accidentally created another object. – Kos Jan 01 '11 at 15:01
  • @Kos: ...and sliced the object it's copied from in the process. Anyway, at least you justify your downvote. – icecrime Jan 01 '11 at 15:07
  • I'll do my best to explain. :) I'm not being offensive or something. It's just that slicing does not happen here. An object of class A is explicitly created and constructed with `A::A(const A&)`. Slicing, as I understood, happens when we want `B::operator=`, on an object of class B, but instead `A::operator=` is used and the object is assigned to "partially". Here the object of class A requested (unintentionally!) and constructed fully. The problem is not calling wrong op=, or wrong copy ctor - but the sheer fact that an undesired copy-construction happens. Hence - not slicing IMO. – Kos Jan 01 '11 at 15:14
  • 1
    @Kos: slicing is typically demonstrated in the context of function parameter passing, where there are no `operator=` involved, only copy constructors. – icecrime Jan 01 '11 at 15:18
  • 1
    @Kos: I agree with @icecrime. Slicing usually refers to _any_ copying - either via a copy constructor or copy assignment operator - where the source type is a more derived type than the destination type. It is usually, but not necessarily, undesirable. – CB Bailey Jan 01 '11 at 15:23
  • Hm. Therefore it appears that I was using the term 'slicing' for only a specific case of it. But I'll still want to emphasize that the OP's main problem was *unintentional copying* (wrong usage of value type). About the 'slicing' term itself, I stand corrected, thanks Icecrime and Charles! – Kos Jan 01 '11 at 15:30
  • Opened http://meta.stackexchange.com/questions/73659/change-a-vote-after-discussion – Kos Jan 01 '11 at 15:50
5

Now that you've modified your code snippet, the problem is clear. Polymorphism (i.e. virtual functions) are only invoked via pointers and references. You have neither of these. A a = XXX does not contain an object of type B, it contains an object of type A. You've "sliced away" the B-ness of the object by doing that pointer cast and dereference.

If you do A *a = new B();, then you will get the expected behaviour.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • Thanks. This helped. Compiler should at least give warning or what. How much I hate C++, wish I could use C#. – mnn Jan 01 '11 at 14:31
  • @mnm: Why should it warn? For all it knows, that's exactly what you meant to do! – Oliver Charlesworth Jan 01 '11 at 14:32
  • That's not really a good explanation IMO - it doesn't mention that **two** objects are created here and there exists a memory leak. There's no such C++ term as "slicing away" (well, there is, but it's more conceptual than technical), what happens here is "copy construction". – Kos Jan 01 '11 at 14:42
  • @Kos: Yes, hence the scare quotes! "Slicing" is the term e.g. Meyers uses to refer to this effect. And yes, you are correct that in the original code, there are two objects created, and there is a memory leak. But in my opinion, the "slicing" (or whatever you wish to call it) is the concept causing the OP's confusion. – Oliver Charlesworth Jan 01 '11 at 14:53
  • Nope, slicing - as I understand it from description linked here- is a fancy name for early-linking the `operator=` and the causes of that. The OP's problem seems to be the lack of understanding what a "value type" is in C++ and how it behaves for objects, since - AFAIK - there's no similar concept in C# (there are only references (different from C++ references) and - in unsafe sections - pointers, right?). Mnn tried to assign to a value as if it was a C#-like reference and the most important thing is to explain to him what actually happened there. – Kos Jan 01 '11 at 14:59
  • @Kos: What do you mean by "early-linking" in this context? Slicing usually refers to any copying where the source object is of a more derived type than the destination object and can occur in explicit initialization of a new object, explicit assignment or in other contexts where an implicit copy occurs such as function parameter passing and function return. – CB Bailey Jan 01 '11 at 15:17
  • @Charles, I agree; see my comment on @icecrime's post. – Kos Jan 01 '11 at 15:50
1

This might do that trick.

A &a = *(A *)new B();
a.func();

Or

A *a = new B();
a->func();
cpx
  • 17,009
  • 20
  • 87
  • 142
  • This works too, but I think using pointers in way Oli Charlesworth described seems more "cleaner". – mnn Jan 01 '11 at 14:32
  • 1
    in C++, *pointer is like a reference (if not totally a reference), it's not a value. so a *pointer_to_A is a A&. – BatchyX Jan 01 '11 at 14:33
1

Virtual dispatch works only with pointer or reference types:

#include <cstdio>
using namespace std;

class A {
public:
  virtual void func() { printf("A::func()"); }
};

class B : public A {
public:
  virtual void func() { printf("B::func()"); }
};

int main() {
  A* a = new B();
  a->func();
}
Philipp
  • 48,066
  • 12
  • 84
  • 109
  • 1
    "works only with pointer or reference types" - true, but it's not really "doesn't work with value types", but rather "doesn't make much sense with value types". If we know that an object is exactly of type A (which holds for value types), then we can always deduce the method correctly in compile-time, so there's no need for late linking. – Kos Jan 01 '11 at 14:50
0

The problem is the deference and casting of B to A with the A a = *(A *)new B();

You can fix it with just removing the *(A *) changing it to (A *a = new B(); ) but I would take it a step further since your variable name is not good for instantiation of B.

It should be

B *b = new B(); 
b->func(); 
gmlacrosse
  • 362
  • 2
  • 8
0

Because you performed slicing when you copied the dynamically allocated object into object a of type A (which also gave you a memory leak).

a should be a reference (A&) instead, or just keep the pointer.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055