Ch16.5: Derived-to-Base Conversion and Slicing

中文(简体)

1. A Derived Object Is a Base Object

Because the base subobject is a real part of the derived object (Ch16.1), a derived object can be used wherever a base object is expected, with no cast:

Target From a derived lvalue What happens
base& base& r = d; binds to the base subobject inside d; no copy
base const& base const& r = d; same, read-only
base* base* p = &d; points at the base subobject; no copy
base base b = d; copies only the base part — this is slicing

The first three are the useful ones: they are free, they preserve the object, and writes through the reference reach the original. The fourth is the trap, and it is the subject of this page.

2. Slicing: the Derived Part Is Thrown Away

base b = d; does not “convert” d; it constructs a new base from the base subobject of d. The new object has the size and the members of a base, so everything the derived class added is gone — silently, with no warning, because for the compiler this is an ordinary copy constructor call.


#include <fast_io.h>
#include <cstdint>

struct base
{
	::std::int_least32_t v{};
};

struct derived : base
{
	::std::int_least32_t extra{};
};

int main()
{
	try
	{
		derived d{};
		d.v = 1;
		d.extra = 2;

		base sliced = d;                       // copies only the base subobject
		::fast_io::io::println("sliced.v=", sliced.v, " sizeof(sliced)=", sizeof(sliced));

		base &ref = d;                         // refers to the base subobject inside d
		ref.v = 5;
		::fast_io::io::println("after ref.v=5: d.v=", d.v, " d.extra=", d.extra);
	}
	catch throws (::std::error e)
	{
		::fast_io::io::perrln(e);
	}
}

Output:


sliced.v=1 sizeof(sliced)=4
after ref.v=5: d.v=5 d.extra=2

sizeof(sliced) is 4: the object is a base, not a derived. ref, by contrast, is the real base subobject — writing ref.v changed d.v, and d.extra is untouched because it was never involved.

3. Where Slicing Happens Without You Writing It

Slicing is not an exotic operation. It happens whenever a derived object is copied into a base-typed value:

In a hierarchy with no virtual functions all of these are at least type-honest: a base value really is a base, and every operation on it is the base's. Once the base has virtual functions, slicing becomes a logic bug as well, because the copy no longer behaves like the original — Chapter 18 takes that up.

4. Passing by Reference, Not by Value

The rule that follows is one line long: a base class in a parameter list is a reference or a pointer, never a value.


inline void report(base const &b) throws;      // uses the object you passed
inline void report(base b) throws;              // uses a copy of its base subobject

inline void mutate(base &b) noexcept;          // writes reach the original

This is also why the fast_io examples pass devices by reference: ::fast_io::io::println(out, ...) takes the output device by forwarding reference, so an obuf_file stays an obuf_file and its buffer is not copied (Ch11.1).

5. Rules of Thumb