I read about the slicing problem in C++ and I tried some examples (I am from Java background). Unfortunatelly, I do not understand some of the behaviour. Currently, I am stucked in this example (alternated example from Efficent C++ Third Edition). Can anybody help me to understand it?
My simple class of a parent:
class Parent
{
public:
Parent(int type) { _type = type; }
virtual std::string getName() { return "Parent"; }
int getType() { return _type; }
private:
int _type;
};
My simple class of a Child:
class Child : public Parent
{
public:
Child(void) : Parent(2) {};
virtual std::string getName() { return "Child"; }
std::string extraString() { return "Child extra string"; }
};
The main:
void printNames(Parent p)
{
std::cout << "Name: " << p.getName() << std::endl;
if (p.getType() == 2)
{
Child & c = static_cast<Child&>(p);
std::cout << "Extra: " << c.extraString() << std::endl;
std::cout << "Name after cast: " << c.getName() << std::endl;
}
}
int main()
{
Parent p(1);
Child c;
printNames(p);
printNames(c);
}
Afte the execution I get:
Name: Parent
Name: Parent
Extra: Child extra string
Name after cast: Parent
I understand the first two lines, because it is the cause of the "slicing". But I do not understand why can I cast the child into parent via the static cast. In the book, there is written that after the slicing, all the specialized information will be sliced off. So I suppose, I cannot cast p into c, because I do not have infomation (in the beginning of the function printNames a new Parent object is created without the additional information).
Additionaly, why I am getting the "Name after cast: Parent" if the cast was successful instead of "Name after cast: Child"?
Your result is an extraordinary stroke of bad luck. This is the result I’m getting:
Name: Parent
Name: Parent
Extra: Child extra string
bash: line 8: 6391 Segmentation fault (core dumped) ./a.out
Here’s what’s happening in your code:
When you pass c
to printNames
, a conversion takes place. In particular, since the pass is by value, you invoke the copy constructor of Parent
, which is implicitly declared and whose code would look like this:
Parent(Parent const& other) : _type{other._type} {}
In other words, you copy the _type
variable of c
and nothing else. You now have a new object of type Parent
(both its static and dynamic type are Parent
) and c
isn’t actually passed into printNames
at all.
Inside the function, you then forcefully convert p
to a Child&
. This cast cannot succeed because p
simply isn’t a Child
, or convertible to one, but C++ doesn’t give you any diagnostic for that (which is actually a shame, since the compiler could trivially prove that the cast is wrong).
Now we’re in the land of undefined behaviour and now everything is allowed to happen. In practice, since Child::extraString
never accesses this
(either implicitly or explicitly), the call to that function just succeeds. The call is done on an illegal object but since the object is never touched, that works (but is still illegal!).
The next call, to Child::getName
, is a virtual call and thus it needs to explicitly access this
(in most implementations, it accesses the virtual method table pointer). And again, because the code is UB anyway, anything can happen. You were “lucky” and the code just grabbed the virtual method table pointer of the parent class. With my compiler, that access obviously failed.
That code's horrific. What's happening:
printNames(c)
slices c
, copy-constructing local p
from the Parent
object embedded in the caller's c
object, then setting p
's pointer to the Parent
virtual dispatch table.
because the data members of Parent
were copied from c
, the type of p
is 2 and the if
branch is entered
Child & c = static_cast<Child&>(p);
effectively tells the compiler "trust me (I'm a programmer) and I know that p
is actually a Child
object that I'd like a reference to", but that's a blatant lie as p
is actually a Parent
object copied from the Child
c
c.extraString()
is found statically (at compile time) by the compiler because it knows c
is a Child
(or further derived type, but c.extraString
isn't virtual
so it can be resolved statically; it's undefined behaviour to do this on a Parent
object, but probably because extraString
doesn't try to access any data that only a Child
object would have, it has ostensibly run "ok" for you
c.getName()
is virtual
, so the compiler uses the object's virtual dispatch table - because the object is really a Parent
it resolves dynamically (at run time) to the Parent::getName
function and produces the associated output
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With