Ch13.4: Access Control: public and private
1. Two Doors
A class has two access levels, written as labels that stay in effect until the next label:
-
public— anybody may use these names. This is the interface: the part of the class you are promising to keep. -
private— only member functions and friends of this class may use these names. This is the implementation: the part you may rewrite next week.
class point
{
public:
void set(::std::int_least32_t x, ::std::int_least32_t y) noexcept; // the interface
::std::int_least32_t sum() const noexcept;
private:
::std::int_least32_t x_{}; // the implementation
::std::int_least32_t y_{};
};
The compiler refuses outside access to a private name, and it says which member and where it was declared:
error: 'int_least32_t point::x_' is private within this context
note: declared private here
The same program with the private members used from inside the class, which is where the access is allowed:
#include <fast_io.h>
#include <cstdint>
using namespace ::fast_io::io;
class point
{
public:
// This is the whole interface: two things you may do to a point.
void set(::std::int_least32_t x, ::std::int_least32_t y) noexcept
{
x_ = x;
y_ = y;
}
::std::int_least32_t sum() const noexcept
{
return x_ + y_;
}
// Inside the class, every object of the class is visible in full.
::std::int_least32_t dx_to(point const &other) const noexcept
{
return other.x_ - x_;
}
private:
// Nobody outside the class can touch these two.
::std::int_least32_t x_{};
::std::int_least32_t y_{};
};
int main()
{
point a{};
a.set(3, 4);
println("a.sum() = ", a.sum());
point b{};
b.set(10, 4);
println("a.dx_to(b) = ", a.dx_to(b));
}
a.sum() = 7
a.dx_to(b) = 7
2. Access Is Granted to the Class, Not to the Object
This is the detail people get wrong. private does not mean
“only I may look at my own data”. It means “only code written
inside this class may look at this data” — any object of
this class, no matter who it belongs to:
::std::int_least32_t dx_to(point const &other) const noexcept
{
return other.x_ - x_; // other is somebody else's point, and it is still fine
}
dx_to is a member function of point, so it stands
inside the wall and can read other.x_ as easily as its own
x_. That is why classes can implement things like comparison,
arithmetic and validation between two objects without exposing anything: the
wall is around the definition, not around a particular object
(Ch13.10 shows the outside-function
version of the same idea).
3. struct and class Are the Same Thing
There is exactly one difference between them: the default access. In a
struct the first members are public; in a
class they are private.
struct a
{
::std::int_least32_t x; // public
};
class b
{
::std::int_least32_t x; // private
};
error: 'int_least32_t b::x' is private within this context
note: declared private here
The convention this tutorial follows, and the one you will see in every library you read:
| Write | When |
|---|---|
struct |
The type is a plain bundle of values with no invariant and no behaviour: coordinates, a range, a key/value pair. Users are expected to read and write the members directly, and there is nothing to protect. |
class |
The type has an invariant, an interface, or behaviour. Everything is private until you decide it should be public. |
struct-style class.
From here on the examples use class and private data, because that
is the interesting case. The keyword is a default, not a different language: a
class with an explicit public: at the top is the same
type as the equivalent struct.
4. A Small Interface Is a Feature
Access control is where you decide the size of your promises. The instinct when learning is to make everything public “so it is flexible”. That instinct is backwards: every public name is something you can never change without breaking somebody else’s code, and every public data member is an invariant you have decided not to keep.
Ask three questions about each name you are about to put after
public::
- Does the caller need it? If the caller can do the job with a member function, the data member stays private.
-
Can the caller misuse it? A public
::std::int_least64_t balancelets anyone write-1000into it. A private one plus a checkingwithdraw()cannot be misused (Ch13.5). - Would changing it break code that is not mine? If yes, it is a promise. Promises should be few, small and true.
reinterpret_cast, and it is not a way to keep secrets in memory.
Its value is that an honest program cannot break a class by mistake, and the
compiler will say so at the exact line.
point a{3, 4}; worked in Chapter 13.1 because the members were
public and the class had no constructor. With private members there is no way to
write those two values from outside. That is not a defect, it is the missing
half of the design: what replaces it is a constructor,
Ch13.6. Until then, the examples
create an object and fill it in through a member function
(a.set(3, 4)).