What Is a Possible Workaround for Object Slicing in C++

What is a possible workaround for object slicing in c++?

You need to store pointers. If these refer to dynamically allocated objects, use smart pointers.

Avoiding object slicing

The fundamental issue is copying an object (which is not an issue in languages where classes are "reference types", but in C++ the default is to pass things by value, i.e. making a copy). "Slicing" means copying the value of a bigger object (of type B, which derives from A) into a smaller object (of type A). Because A is smaller, only a partial copy is made.

When you create a container, its elements are full objects of their own. For example:

std::vector<int> v(3);  // define a vector of 3 integers
int i = 42;
v[0] = i; // copy 42 into v[0]

v[0] is an int variable, just like i.

The same thing happens with classes:

class Base { ... };
std::vector<Base> v(3); // instantiates 3 Base objects
Base x(42);
v[0] = x;

The last line copies the contents of the x object into the v[0] object.

If we change the type of x like this:

class Derived : public Base { ... };
std::vector<Base> v(3);
Derived x(42, "hello");
v[0] = x;

... then v[0] = x tries to copy the contents of a Derived object into a Base object. What happens in this case is that all members declared in Derived are ignored. Only the data members declared in the base class Base are copied, because that's all v[0] has room for.

What a pointer gives you is the ability to avoid copying. When you do

T x;
T *ptr = &x;

, ptr is not a copy of x, it just points to x.

Similarly, you can do

Derived obj;
Base *ptr = &obj;

&obj and ptr have different types (Derived * and Base *, respectively), but C++ allows this code anyway. Because Derived objects contain all members of Base, it's OK to let a Base pointer point at a Derived instance.

What this gives you is essentially a reduced interface to obj. When accessed through ptr, it only has the methods declared in Base. But because no copying was done, all data (including the Derived specific parts) are still there and can be used internally.

As for virtual: Normally, when you call a method foo through an object of type Base, it will invoke exactly Base::foo (i.e. the method defined in Base). This happens even if the call is made through a pointer that actually points at a derived object (as described above) with a different implementation of the method:

class Base {
public:
void foo() const { std::cout << "hello from Base::foo\n"; }
};

class Derived : public Base {
public:
void foo() const { std::cout << "hello from Derived::foo\n"; }
};

Derived obj;
Base *ptr = &obj;
obj.foo(); // calls Derived::foo
ptr->foo(); // calls Base::foo, even though ptr actually points to a Derived object

By marking foo as virtual, we force the method call to use the actual type of the object, instead of the declared type of the pointer the call is made through:

class Base {
public:
virtual void foo() const { std::cout << "hello from Base::foo\n"; }
};

class Derived : public Base {
public:
void foo() const { std::cout << "hello from Derived::foo\n"; }
};

Derived obj;
Base *ptr = &obj;
obj.foo(); // calls Derived::foo
ptr->foo(); // also calls Derived::foo

virtual has no effect on normal objects because there the declared type and the actual type are always the same. It only affects method calls made through pointers (and references) to objects, because those have the ability to refer to other objects (of potentially different types).

And that is another reason to store a collection of pointers: When you have several different subclasses of GameObject, all of which implement their own custom draw method, you want the code to pay attention to the actual types of the objects, so the right method gets called in each case. If draw weren't virtual, your code would attempt to invoke GameObject::draw, which doesn't exist. Depending on how exactly you code it, this either wouldn't compile in the first place or abort at runtime.

A potential workaround for C++ slicing?

_foo is a FOO&.

&_foo is a FOO*.

The compiler cannot convert from 'FOO *' to 'FOO &'.

what you want is ..., _realFoo(_foo)

Unrelated: What you probably actually want is a std::unique_ptr<FOO> member instead. This will be much smaller, and much less error prone.

The struct as you have it now, contains a full and complete instance of FOO, and a full and complete instance of FOOSUBCLASS, and a reference. At least put the two FOO thingies in a union, so the size of BAR is only slightly bigger than the biggest FOO derivative. That would probably use the same memory as a unique_ptr<FOO>. Unfortunately, unions are a common source of bugs in C and C++.

Another problem is if someone comes along and writes

class FOOSOMETHINGELSE : public FOO {
int buffer[1024];
};

Then you'll have to go and find the BAR class and change it and make it even bigger, and recompile all of your code that uses BAR everywhere. Whereas, if you used a unique_ptr<FOO>, then you won't have to change BAR or recompile anything. So smaller chance of error.

Member variables and object slicing

Changing the classes

The class definitions for Base and Derived should be changed to:

class Base {
public:
int x = 1;

virtual void f() { }
};

class Derived : public Base {
public:
Derived() : Base() { x = 2; }
int y = 55;

void f() { }
};

The notable addition is the virtual function void f() to allow dynamic_cast to handle casting the pointers.

Using unique_ptr

Unique pointers can be used to store, retrieve, modify and safely delete pointers to Derived objects residing within a vector of pointers to Base objects. The example below uses heap-allocated objects.

/* Insertion */
// Create a vector of unique_ptr to Base and add a unique_ptr to Derived to it
std::vector<std::unique_ptr<Base>> v;
std::unique_ptr<Derived> p1(new Derived());
v.push_back(std::move(p1));

/* Retrieval */
// Release the pointer at base to place it into p2
std::unique_ptr<Derived> p2(dynamic_cast<Derived*>(v[0].release()));

/* Modification */
p2->x = 0xff; // Modify x (Decimal 255)
p2->y = 0xffff; // Modify y (Decimal 65535)
int y = p2->y; // Copy the value of the Derived object's y to an int for use later

// Move the pointer to the modified object back to v
v[0] = std::move(p2);

/* Output */
std::cout << v[0]->x << std::endl; // Outputs 255
std::cout << y << std::endl; // OUtputs 65535

/* Delete */
// To safely delete the object, it must be treated as a pointer to a Derived object
// Thus it must be released from the control of v and deleted via p3, which is cast in the same manner as p2 was
std::unique_ptr<Derived> p3(dynamic_cast<Derived*>(v[0].release()));
p3.reset(); // This deletes the object as well, not just the reference to it!

Functions as expected and outputs 255 and 65535.

Object slicing : pass Derived as Base by value - safe or dangerous?

Resharper acts in a contradict way from my belief.

it acts in a correct way, lets see:

Warning when the type is value.

this is for this code:

class B1{ int field1=0; };  
class B2{ int field2=0; };
class C: public B1,public B2{ };
class Test{
void f(B1 b){ }
void f2(){
C c;
f(c);
}
};

in a function call, value initialization of variable b of type B1 from variable c of type C which is derived from B1, will execute a copy constructor of B1 whose signature is B1(const B1& rhs);, in this constructor (whether it is auto generated or not) only fields present in B1 will be copied from rhs, so the rest (part from B2 or C) will get sliced.

No warning when the type is reference (change #2 from B1 to B1&).

void f(B1& b){ } 

that is correct, this is how polymorphic objects should be passed around, by assigning objects to pointers or references of base class types.

Always no warning when C derived from only B1, no matter #2 is. (B or B&)

thats only because C has no fields, so there is no slicing in this case. Try adding int n; to C and warning will be shown again.

C++: Is there a more elegant solution to this (multiple dispatch) runtime polymorphism?

Here is a simplified example (untested) of the classical double dispatch.

struct Circle;
struct Rectangle;

struct Shape {
virtual bool intersect (const Shape&) const = 0;
virtual bool intersectWith (const Circle&) const = 0;
virtual bool intersectWith (const Rectangle&) const = 0;
};

struct Circle : Shape {
bool intersect (const Shape& other) const override {
return other.intersectWith(*this);
}
bool intersectWith (const Circle& other) const override {
return /* circle x circle intersect code */;
}
bool intersectWith (const Rectangle& other) const override {
return /* circle x rectangle intersect code*/;
}
};

struct Rectangle : Shape {
bool intersect (const Shape& other) const override {
return other.intersectWith(*this);
}
bool intersectWith (const Circle& other) const override {
return /* rectangle x circle intersect code */;
}
bool intersectWith (const Rectangle& other) const override {
return /* rectangle x rectangle intersect code*/;
}
};

As you can see you weren't too far off.

Notes:

  1. return intersectWith(*this); needs to be repeated in each derived class. The text of the method is the same every time, but the type of this is different. This can be templatized to avoid repetition, but it probably isn't worth it.
  2. The Shape base class (and, naturally, each of its derived classes) needs to know about all Shape-derived classes. This creates a cyclic dependency between classes. There are ways to avoid it but these do require casting.

This is not the solution of the multiple-dispatch problem, but it is a solution. A variant-based solution may or may not be preferable, depending on what other stuff you have in your code.

Object slicing when using std::enable_if

@Sebastian's answer explains the problem, but the suggested solution is going to be problematic: you can't specialize a base class template on properties of the derived class using CRTP, since the derived class isn't complete when the base class is instantiated. I would suggest that you instead always override f1 in A, and use tag dispatching to determine whether to dispatch to f2 in the derived class or the default implementation in Base:

template<typename Derived>
class A : public Base
{
double f1_impl(boost::mpl::true_, double x, double) const
{
std::cout << "Called Derived Method\n";
return static_cast<const Derived*>(this)->f2(x);
}
double f1_impl(boost::mpl::false_, double x, double y) const
{
return Base::f1(x, y);
}

public:
double f1(double x, double y) const override
{
using has_f2 = typename has_member_function_f2
< Derived
, double
, boost::mpl::list<double>
, boost::function_types::const_qualified
>::type;
return f1_impl(has_f2{}, x, y);
}
};

DEMO

Curious case of C++ Object slicing behaviour on Base class static reference data member

When you do

Base::base=Derived::derived;

You are not setting the base reference to refer to the derived class. This is the assignment operator and all it does is asign the Base part of derived to base.

base is still of type Base and can never change as references can only ever be initialized once and you do that with

Base& Base::base=Base::getBaseInstance();

If you want this reassignment behavior you are going to need to use a pointer type.

Pass by reference avoids object slicing

The derived object gets "sliced" when it is used to instantiate a base class object. This happens when you pass by value, because the function parameter is a base class object, not anything else. It is the equivalent of doing this:

Derived d;
Base b = d; // b is a Base, not a Derived. It knows nothing of Derived.

A reference is simply an alias for an object, so a reference to a Base object does not involve the construction of a new Base object. It simply aliases one:

Base& b = d; // b aliases d, i.e. a Derived object

After the above statement, b is an alias for d and can be use to access d's Base interface polymorphically. It can alias a Derived object because Derived is-a Base. This wouldn't be possible with, say, private inheritance.



Related Topics



Leave a reply



Submit