Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slicing in C++ where I am wrong?

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"?

like image 769
napets Avatar asked Dec 25 '22 06:12

napets


2 Answers

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.

like image 149
Konrad Rudolph Avatar answered Dec 28 '22 07:12

Konrad Rudolph


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

    • the onus is on you as the programmer to not ask the compiler to do this if you're not sure it's valid
  • 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

    • the implementation of virtual dispatch is implementation defined though, and your undefined behaviour might not happen to function this way on all C++ implementations, or even at all optimisation levels, with all compiler options etc.
like image 20
Tony Delroy Avatar answered Dec 28 '22 06:12

Tony Delroy