Ch16.1: What Inheritance Is: the Base Subobject

中文(简体)

1. A Derived Object Contains a Base Object

struct labeled_point : point does not copy point's code into labeled_point. It makes every labeled_point contain a point, as a part of itself, and calls that part the base subobject. Everything else about inheritance follows from that one sentence.


struct point
{
	::std::int_least32_t x{}, y{};

	[[nodiscard]] ::std::int_least32_t sum() const noexcept { return x + y; }
};

struct labeled_point : point
{
	::std::string_view label{};
};

The relation is written from the derived side, and the access specifier in front of the base name is not decoration — it is public, protected or private, and it changes what the rest of the program may do with the relationship (Ch16.3).

2. The is-a Test

Public inheritance is the strongest statement about a type that C++ has: it says the derived type is a base type, for every purpose, forever. Hold the sentence and check it:

Candidate “A D is a B” Verdict
labeled_point : point a labeled point is a point yes — the label is extra, everything a point guarantees still holds
point : labeled_point a point is a labeled point no — a plain point has no label
car : engine a car is an engine no — a car has an engine (Ch16.8)
square : rectangle a square is a rectangle tempting and wrong — a rectangle can be resized to 2×3, a square cannot, so a square& passed as a rectangle& would be breakable

The last row is the classic reason to be strict. Public inheritance means the base class's promises hold for the derived object for all callers, including ones written before the derived class existed. “Is-a” is not about the real world; it is about substitutability.

3. Layout: the Base Comes First

A base subobject sits at the start of a derived object with no virtual inheritance involved, which is why a base reference to a derived object needs no adjustment:

Expression Type Value
sizeof(point) ::std::size_t 8
sizeof(labeled_point) ::std::size_t 24
sizeof(std::string_view) ::std::size_t 16

No padding is added for the inheritance itself: a derived class with no new data members is exactly as large as its base. That is the empty base optimisation's business and this chapter's last-but-one surprise (Ch16.8).

4. The Whole Program


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

struct point
{
	::std::int_least32_t x{}, y{};

	[[nodiscard]] ::std::int_least32_t sum() const noexcept { return x + y; }
};

struct labeled_point : point
{
	::std::string_view label{};
};

int main()
{
	try
	{
		labeled_point p{};
		p.x = 3;
		p.y = 4;
		p.label = "corner";

		::fast_io::io::println(p.label, " ", p.sum());
		::fast_io::io::println("sizeof(point)=", sizeof(point), " sizeof(labeled_point)=", sizeof(labeled_point));

		point const &base{p};
		::fast_io::io::println("base.sum()=", base.sum());
	}
	catch throws (::std::error e)
	{
		::fast_io::io::perrln(e);
	}
}

Output:


corner 7
sizeof(point)=8 sizeof(labeled_point)=24
base.sum()=7

The third line is the interesting one: base is a reference to the point subobject inside p, not to a copy, and it was created without a cast.

5. Rules of Thumb