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:


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.
Everything you have seen so far in this chapter was a 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::

  1. Does the caller need it? If the caller can do the job with a member function, the data member stays private.
  2. Can the caller misuse it? A public ::std::int_least64_t balance lets anyone write -1000 into it. A private one plus a checking withdraw() cannot be misused (Ch13.5).
  3. Would changing it break code that is not mine? If yes, it is a promise. Promises should be few, small and true.
Access control is a compile-time rule, not security. It stops accidents and it stops misuse by people writing normal code. It does not stop a determined programmer with a pointer and a 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.
The bill for closing the wall arrives immediately. 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)).