Ch13.1: What a Class Is: Data and the Functions That Belong to It
1. Data, and the Functions That Work on It
Here is a point, written the way you have written everything so far: a
struct with two members, and a free function that takes one:
struct point_data
{
::std::int_least32_t x;
::std::int_least32_t y;
};
::std::int_least32_t sum_of(point_data const &p) noexcept
{
return p.x + p.y;
}
This works, and it has a real weakness that has nothing to do with style:
nothing connects the two. The function has to name the type
twice, and it has to be written in a place where the type is visible. Nothing
in the compiler knows that sum_of is the sum of a point,
so there is no way to discover the operations a point supports — you
grep for them and hope.
Now the same thing as a class. The two integers and the two
operations live inside one type, and the operations are called with the dot
syntax you already use for ::fast_io::string members:
#include <fast_io.h>
#include <cstdint>
using namespace ::fast_io::io;
// The old way: the data and the functions that work on it are separate.
struct point_data
{
::std::int_least32_t x;
::std::int_least32_t y;
};
::std::int_least32_t sum_of(point_data const &p) noexcept
{
return p.x + p.y;
}
// A class: the data and the operations that belong to it, together, in one type.
class point
{
public:
::std::int_least32_t x{};
::std::int_least32_t y{};
::std::int_least32_t sum() const noexcept
{
return x + y;
}
::std::int_least32_t product() const noexcept
{
return x * y;
}
};
int main()
{
point a{3, 4};
point b{-2, 5};
println("a.sum() = ", a.sum());
println("a.product() = ", a.product());
println("b.sum() = ", b.sum());
println("b.product() = ", b.product());
point_data d{3, 4};
println("sum_of(d) = ", sum_of(d));
}
a.sum() = 7
a.product() = 12
b.sum() = 3
b.product() = -10
sum_of(d) = 7
Nothing was added to the language. sum() is an ordinary function
that happens to be declared inside the class, and a.sum() is an
ordinary function call that also says on which object to run it. That
is the whole mechanism: a member function is a function with one extra,
invisible argument — the object (Ch13.7).
2. An Object Is a Value Like Any Other
This is the part that surprises people coming from languages where objects are
special: here a point is not a reference, a handle, or a pointer.
It is its two integers, stored where you put it, with no extra
allocation. So everything you learned about values keeps working:
point a{3, 4};— the object lives on the stack, 8 bytes, nothing on the heap.point ps[3]{{1, 2}, {3, 4}, {5, 6}};— an array of them is just an array.point const &p— passing a point to a function passes its address, andconstsays the function will not change it.for (auto const &p : ps)— a range-for over an array of points works like a range-for over an array of anything.
#include <fast_io.h>
#include <cstdint>
#include <cstddef>
using namespace ::fast_io::io;
class point
{
public:
::std::int_least32_t x{};
::std::int_least32_t y{};
::std::int_least32_t sum() const noexcept
{
return x + y;
}
};
::std::int_least32_t total(point const *ps, ::std::size_t n) noexcept
{
::std::int_least32_t sum{};
for (::std::size_t i{}; i != n; ++i)
{
sum += ps[i].sum();
}
return sum;
}
int main()
{
point ps[3]{{1, 2}, {3, 4}, {5, 6}};
for (auto const &p : ps)
{
println(p.x, " + ", p.y, " = ", p.sum());
}
println("total = ", total(ps, 3));
}
1 + 2 = 3
3 + 4 = 7
5 + 6 = 11
total = 21
Notice that total is a free function that knows nothing about
points beyond what the class shows it: ps[i].sum(). That is the
usual shape of a program built out of classes — small types that know a
lot about themselves, and free functions that combine them.
3. What the Class Actually Buys You
| With a struct and free functions | With a class and member functions |
|---|---|
| The operations are scattered; finding them means searching the program. | The operations are listed inside the type. Its definition is its documentation. |
Any code anywhere can set p.x = -999 and break whatever
the data was supposed to mean. |
You decide who may touch x
(Ch13.4). |
| A new invariant (“coordinates are never negative”) has to be checked at every call site. | The invariant is checked once, inside the class (Ch13.5). |
| Changing the representation means changing every function that took the struct apart. | Only the class changes; the interface stays (Ch13.5). |
| Nothing forces the object to start in a sensible state. | Constructors force it (Ch13.6). |
point a{3, 4}; only works like that because the
class has no constructor and all members are public, so the braces initialize
the members in declaration order. Chapter 13.4 closes the door and Chapter 13.6
gives you the constructor that replaces this syntax. From then on, the way to
make an object is a constructor call, not a brace list you have to keep in sync
by hand.