Categories
c++ c++-faq inheritance object-slicing

What is object slicing?

857

In c++ what is object slicing and when does it occur?

0

    588

    Most answers here fail to explain what the actual problem with slicing is. They only explain the benign cases of slicing, not the treacherous ones. Assume, like the other answers, that you’re dealing with two classes A and B, where B derives (publicly) from A.

    In this situation, C++ lets you pass an instance of B to A‘s assignment operator (and also to the copy constructor). This works because an instance of B can be converted to a const A&, which is what assignment operators and copy-constructors expect their arguments to be.

    The benign case

    B b;
    A a = b;
    

    Nothing bad happens there – you asked for an instance of A which is a copy of B, and that’s exactly what you get. Sure, a won’t contain some of b‘s members, but how should it? It’s an A, after all, not a B, so it hasn’t even heard about these members, let alone would be able to store them.

    The treacherous case

    B b1;
    B b2;
    A& a_ref = b2;
    a_ref = b1;
    //b2 now contains a mixture of b1 and b2!
    

    You might think that b2 will be a copy of b1 afterward. But, alas, it’s not! If you inspect it, you’ll discover that b2 is a Frankensteinian creature, made from some chunks of b1 (the chunks that B inherits from A), and some chunks of b2 (the chunks that only B contains). Ouch!

    What happened? Well, C++ by default doesn’t treat assignment operators as virtual. Thus, the line a_ref = b1 will call the assignment operator of A, not that of B. This is because, for non-virtual functions, the declared (formally: static) type (which is A&) determines which function is called, as opposed to the actual (formally: dynamic) type (which would be B, since a_ref references an instance of B). Now, A‘s assignment operator obviously knows only about the members declared in A, so it will copy only those, leaving the members added in B unchanged.

    A solution

    Assigning only to parts of an object usually makes little sense, yet C++, unfortunately, provides no built-in way to forbid this. You can, however, roll your own. The first step is making the assignment operator virtual. This will guarantee that it’s always the actual type’s assignment operator which is called, not the declared type’s. The second step is to use dynamic_cast to verify that the assigned object has a compatible type. The third step is to do the actual assignment in a (protected!) member assign(), since B‘s assign() will probably want to use A‘s assign() to copy A‘s, members.

    class A {
    public:
      virtual A& operator= (const A& a) {
        assign(a);
        return *this;
      }
    
    protected:
      void assign(const A& a) {
        // copy members of A from a to this
      }
    };
    
    class B : public A {
    public:
      virtual B& operator= (const A& a) {
        if (const B* b = dynamic_cast<const B*>(&a))
          assign(*b);
        else
          throw bad_assignment();
        return *this;
      }
    
    protected:
      void assign(const B& b) {
        A::assign(b); // Let A's assign() copy members of A from b to this
        // copy members of B from b to this
      }
    };
    

    Note that, for pure convenience, B‘s operator= covariantly overrides the return type, since it knows that it’s returning an instance of B.

    29

    • 13

      IMHO, the problem is that there are two different kinds of substitutability that may be implied by inheritance: either any derived value may be given to code expecting a base value, or any derived reference may be used as a base reference. I would like to see a language with a type system which addresses both concepts separately. There are many cases where a derived reference should be substitutable for a base reference, but derived instances should not be substitutable for base ones; there are also many cases where instances should be convertible but references should not substitute.

      – supercat

      Aug 12, 2013 at 16:11


    • 23

      I don’t understand what is so bad in your “treacherous” case. You stated that you want to: 1) get a reference to an object of class A and 2) cast the object b1 to class A and copy its stuff to a reference of the class A. What is actually wrong here is the proper logic behind the given code. In other words, you took a small image frame (A), placed it over a bigger image (B) and you painted through that frame, complaining later that your bigger image now looks ugly 🙂 But if we just consider that framed area, it looks pretty good, just as the painter wanted, right? 🙂

      – Mladen B.

      Nov 14, 2013 at 13:05

    • 13

      The problem is, differently put, that C++ by default assumes a very strong kind of substitutability – it requires the base class’es operations to workly correctly on subclass instances. And that even for operations which the compiler autogenerated like assignment. So it’s not enough to not screw up your own operations in this regard, you also have to explicitly disable the wrong ones generated by the compiler. Or of course, stay away from public inheritance, which usually is a good suggestion anway 😉

      – fgp

      Nov 16, 2013 at 16:31

    • 16

      Another common approach is to simply disable the copy and assignment operator. For classes within inheritance hierarchy, usually there is no reason to use value instead of reference or pointer.

      Aug 22, 2014 at 10:48

    • 18

      What the? I had no idea operators could be marked virtual

      – paulm

      Mar 2, 2015 at 15:23

    168

    If You have a base class A and a derived class B, then You can do the following.

    void wantAnA(A myA)
    {
       // work with myA
    }
    
    B derived;
    // work with the object "derived"
    wantAnA(derived);
    

    Now the method wantAnA needs a copy of derived. However, the object derived cannot be copied completely, as the class B could invent additional member variables which are not in its base class A.

    Therefore, to call wantAnA, the compiler will “slice off” all additional members of the derived class. The result might be an object you did not want to create, because

    • it may be incomplete,
    • it behaves like an A-object (all special behaviour of the class B is lost).

    15

    • 53

      C++ is not Java! If wantAnA (as its name implies!) wants an A, then that’s what it gets. And an instance of A, will, uh, behave like an A. How is that surprising?

      – fgp

      Jan 22, 2013 at 16:39

    • 90

      @fgp: It’s surprising, because you don’t pass an A to the function.

      – Black

      Mar 3, 2013 at 7:03

    • 11

      @fgp: The behaviour is similar. However, to the average C++ programmer it might be less obvious. As far as I understood the question, nobody is “complaining”. It’s just about how the compiler handles the situation. Imho, it is better to avoid slicing at all by passing (const) references.

      – Black

      Apr 7, 2013 at 12:13

    • 9

      @ThomasW No, I would not throw out inheritance, but use references. If the signature of wantAnA would be void wantAnA(const A & myA), then there had been not slicing. Instead, a read-only reference to the caller’s object is passed.

      – Black

      May 28, 2013 at 7:44

    • 15

      the problem is mostly on the automatic casting that the compiler performs from derived to the type A. Implicit casting is always a source of unexpected behavior in C++, because it is often hard to understand from looking at the code locally that a cast took place.

      – pqnet

      Aug 6, 2014 at 23:15