Does Polymorphic Slicing occur when Derived Object declares no New Members

Viewed 96

Does slicing occur in polymorphism, if you store a derived class by value, but the derived class has no extra members?

For example, if I have a class Base. But I want a custom constructor, so I implement Custom, like so:

class Base{
public:
    int a;
    int b;
    Base(int a) 
        : a(a) 
    { b = 2; }
};
class Custom : public Base{
public: // Note: no new members or functions!
    Custom(int bb) 
        : Base(1) 
    { b = bb; }
};

If I run it with the following code, it all works as it should. The two instances of Base is initialized correctly. They

#include <stdio.h>
int main(){
    Base base = Base(1);
    Base sliced = Custom(2);
    Custom custom = Custom(2);

    // Some printing of `a`, `b`, and sizeof(base), etc
}

It prints out the following, as you would expect:

         |   a |   b | Size                                                         
-----------------------------
    base |   1 |   2 |   8                                                          
  sliced |   1 |   2 |   8                                                          
  custom |   1 |   2 |   8   

So, if I could ask more specifically:

  1. Will this always work? I.e. will I lose information of a or b? (any members/functions of Base)
  2. If not, why not? Does a derived object store extra information?
  3. If this will always work, is it bad practice?

I have looked into using strong types, as described by this blog by FluentCpp. This will also work for my implementation, but I feel this makes the code more bloated.

3 Answers

Does slicing occur in polymorphism, if you store a derived class by value, but the derived class has no extra members?

Yes.

It's quite another matter that you don't see any significant loses in your posted code. The metrics you are using to compare the objects indicate as though nothing got lost because of object slicing. However, if you look at the objects in a debugger, you will most likley notice that objects are of different types. The effects of object slicing become apparent in that environment.

I would recommend not creating a derived class just for the convenience of constructing an object of the base class. Use of one or more non-member functions is more appropriate.

class Base
{
   public:
      int a;
      int b;
      Base(int a, int b = 2) : a(a) , b(b) {}
};

Base fromA(int a)
{
   return {a};
}

Base fromB(int b)
{
   return {1, b};
}

Using inheritance for the task that you seem to be using it for is not a good idea. Note that from c++20, you can solve the problem you've shown with designated initializers:

class Base 
{
  public:
    int a = 1;
    int b = 2;
};

int main() 
{
    Base a = Base{.a = 10};  // default b
    Base b = Base{.b = 20};  // default a
    Base c = Base{.a = 10, .b = 20};
}

The main reason for having derived classes is to have virtual functions. With your code, once the object is sliced, the virtual functions will start calling the base class versions:

https://godbolt.org/z/WoT5qb

#include <iostream>

class Base{
public:
    int a;
    int b;
    Base(int a) 
        : a(a) 
    {
        b = 2; 
    }

    virtual void foo()
    {       
        std::cout << "Base" << std::endl;
    }
};
class Custom : public Base{
public: // Note: no new members!
    Custom(int bb) 
        : Base(1) 
    { b = bb; }

    virtual void foo()
    {       
        std::cout << "Custom" << std::endl;
    }
};

int main(){
    Base base = Base(1);
    Base sliced = Custom(2);
    Custom custom = Custom(2);

    base.foo();
    custom.foo();
    sliced.foo();
}

which outputs:

Base
Custom
Base

So, slicing is still very much undesirable.

Related