bolov bolov - 2 months ago 17
C++ Question

Does object slicing break encapsulation?

The way I see it, in OOP one role of public interface is to make sure that the object is always in a valid state (that's one important reason why you can't

memcpy
to a non-pod type). E.g. a very basic, pedantic example: (please ignore the java-like set/get):

class Restricted {
protected:
int a;

public:
Restricted() : a{0} {}

// always returns a non-negative number
auto get() const -> int {
return a;
}

auto set(int pa) -> void {
if (pa < 0)
a = 0;
else
a = pa;
}

auto do_something() {
// here I code with the assumption that
// a is not negative
}
};


In this example, class
Restricted
is modeled in such a way that a
Restricted
object always holds a non-negative number. That is how I define a valid state for
Restricted
. By looking at the interface I can say that
Restricted ::get
will always return a non-negative number. There is no way the user can get
Restricted
to hold a negative number.

a
is made protected to let the option to easily extend the class. So let us extend
Restricted
with a type that allows all numbers:

class Extended : public Restricted {
public:
Extended() { a = -1; }

auto set(int pa) -> void {
a = pa;
}

auto do_something() {
// now a can be negative, so I take that into account
}
};


At a first glance all is ok.
Extended
doesn't modify
Restricted
or it's behavior.
Restricted
is still the same and we just add another type that also allows negative numbers.

Except that our initial assumption for
Restricted
doesn't hold up anymore. The user can easily obtain an
Restricted
object that holds a negative number (an object in an invalid state) because:

C++
allows object slicing without any warning:

Restricted r = Extended{};

// or

Extended e;
e.set(-24);

Restricted r = e;

// and then:

r.do_something(); // oups


Something doesn't add up:


  • was it wrong to create
    Extended
    as a subclass of
    Restricted
    ? If so, why?

  • was it wrong to set
    a
    as
    protected
    if I expect
    a
    to be always non-negative? Why?
    protected
    shouldn't allow a change of behavior in my class.

  • is it wrong for
    C++
    to allow object slicing?

  • all the above

  • something else


Answer

was it wrong to set a as protected if I expect a to be always non-negative? Why?

Yes, it is wrong. Restricted's interface requires that a be non-negative. That is the invariant that the type sets up. And it has no virtual functions to permit a derived class from overriding that invariant.

By violating that invariant (and because of your curious lack of virtual functions), Extended is breaking a basic rule of OOP: a derived class instance should be able to be treated like an instance of a (public) base class. That doesn't mean slicing; I mean, you should be able to pass an Extended to a function that takes a pointer/reference to a Restricted, and everything should work as if it were talking to the Extended.

was it wrong to create Extended as a subclass of Restricted? If so, why?

It was wrong to:

  1. Make a protected instead of private.

  2. Make Restricted's interface non-virtual.

is it wrong for C++ to allow object slicing?

No.