10

I have looked at the related questions such as here and here about this topic, and they all describe object slicing, but none of them address whether it is safe, reliable and predictable.

Is there a guarantee from either the standard or the compiler, that if I pass a subclass object by value to a method which wants a superclass, the parts that are sliced off are exactly the members of the subclass, and I will be able to use the sliced superclass object without any concerns about undefined behavior?

Community
  • 1
  • 1
merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • 1
    Well defined may be, but easily punched by splicing problems! – πάντα ῥεῖ Apr 16 '14 at 20:05
  • 3
    Slicing happens through the use of the base class' copy constructor. Hence, the issue actually is binding an object of a derived class type to a reference of the base class type, which is well-defined and not problematic (for single inheritance). However, *you* have to make sure the copy ctor works properly. – dyp Apr 16 '14 at 20:06

2 Answers2

6

Yes, it is safe, reliable, and predictable, because it is well defined by the standard (it will just copy construct a base class object from the derived class object).

But no, it is not safe, it should not be relied on, and generally be treated as unpredictable, because your readers won't know what's going on. This will trigger a lot of bugs when others try to modify your code later (including your own future self). It is basically a no-no, much in the same way as the goto statement, which is perfectly well defined, reliable, and predictable as well.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
3

When they say "parts are sliced off" they do not mean that these parts somehow "disappear": all they mean that these parts are not copied into the object presented to your function for the corresponding parameter. In this sense, object slicing is not dangerous or poorly defined.

What happens there is rather straightforward: in order to construct the value of the parameter that you are passing by value, the object of the derived class used for the actual parameter is given to the constructor of the base class to make a copy. Once the copy constructor has completed its work, you have an object of the base class.

At this point you have a fully functioning object of the base class. The compiler guards you against accepting a class with pure virtual members by value, so you wouldn't be able to slice your object into an instance with missing pure virtual functions.

A more important question is whether you want the slicing behavior to happen implicitly: there is nothing the compiler does here that you wouldn't be able to do in your code:

void foo(bar b) {
    ... // Payload logic
}

gives you the same functionality as

void foo(const bar &r) {
    bar b(r);
    ... // Payload logic
}

With the first snippet, it is very easy to miss the fact that an ampersand is missing after the name of a type, leading the readers to think that the polymorphic behavior is retained, while it is, in fact, lost. The second snippet is easier to understand to people who maintain your code, because it makes a copy explicitly.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523