Skip to content
VLSI Mentor

C++ Part I · Module 11 · Classes and Encapsulation

Access Control: public, private and protected

Three chapters have used public and private without defining them, which is a larger debt than it sounds, because Chapter 11.1's entire argument rests on those two labels. The claim there was that a class can guarantee something about its data; the mechanism for that guarantee is not the constructor and not the member functions — both of which a struct can have — but the fact that the data cannot be reached from outside at all. This chapter is that mechanism. There are three specifiers, their meanings are short, and the only one that needs care is protected, because it is meaningless in isolation: it says nothing about who may touch a member until a second class exists that inherits from the first, so the honest way to show it is a matrix rather than a sentence. The chapter also settles the question people ask first about these two keywords, which is what actually differs between class and struct, and the answer is pleasingly small. And it ends on a question with a less comfortable answer. Access specifiers look like pure compile-time bookkeeping with no effect on the object's layout, and a measurement of three members across two access sections appeared to confirm exactly that — until the compiler volunteered a warning that reframed the result, and which turned out to be more informative than the numbers.

Foundation16 min readC++Part IFundamentalsClasses

Part I · Module 11 · Chapter 11.4 · Access Control: public, private and protected

1. The Debt This Chapter Pays

Chapter 11.1 §7 stated the module's thesis:

"A class is not a struct with functions attached. It is a type that owns an invariant, and access control is the mechanism that keeps the invariant enforceable."

Chapters 11.2 and 11.3 then used public: and private: in every listing without defining them. This chapter defines them.

2. The Three Specifiers

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Field {
    public:        // reachable from anywhere
        int width() const;
    protected:     // reachable from this class and classes derived from it
        void recheck();
    private:       // reachable only from this class
        int width_;
    };

An access specifier applies to everything after it until the next one, and they can appear any number of times in any order. Measured from outside the class, one at a time:

SpecifierAccess from outside
publicOK
protectederror: 'x' is a protected member of 'C'
privateerror: 'x' is a private member of 'C'

public and private are the two you will use. protected needs §5.

Two things that are not restricted, worth stating because they surprise people:

The class's own member functions can reach all of it, regardless of specifier. That is the point — Chapter 11.3 §7's rule that mutating operations must be members depends on it.

Access is per-class, not per-object. A member function of Field can reach the private members of any Field, not just its own:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    bool Field::overlaps(const Field& other) const
    {
        return offset_ < other.offset_ + other.width_      // other.offset_ is private
            && other.offset_ < offset_ + width_;           // and reachable anyway
    }

That is what makes comparison and combination operations writable at all.

3. What private Buys, Restated

Chapter 11.1 §6 measured this and it is the reason the specifier exists:

Attempt from outsideResult
f.width_ = 100;error: 'width_' is a private member of 'Field'
f.offset_ = -1;error: 'offset_' is a private member of 'Field'
int* p = &f.width_;error: 'width_' is a private member of 'Field'

The third row is the one that makes it a real barrier rather than a speed bump: you cannot even take the address of a private member from outside, so the route Chapter 8.2 established — get a pointer, write through it — is closed at the first step.

4. class Versus struct: Exactly One Difference

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct S { int x; };    s.x = 1;    // OK
    class  C { int x; };    c.x = 1;    // error
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: 'x' is a private member of 'C'
note: implicitly declared private here

The note is the informative half: implicitly. No specifier appeared in class C, and the default supplied one.

struct members default to public; class members default to private. That is the only difference between the two keywords.

Everything else is identical, and Chapter 11.1 §4 measured it: sizeof 8 for both, alignof 4 for both, and both can have member functions, constructors, access specifiers and invariants. A struct with private: in it is a class in every respect except the keyword.

(One further default differs for inheritance — a struct's base is public by default and a class's is private — which is Chapter 16.3's subject and named here only for completeness.)

So which keyword should you write? The technical answer is that it does not matter; the useful answer is that the keyword is a signal to the reader, and the convention this curriculum follows is the common one:

struct when the members are independent data with no invariant — Chapter 9.7's Field, Chapter 9.11's Signal. Write no access specifiers; the public default is what you want.

class when the type owns an invariant — Chapter 11.1 §5's Field. The private default is then what you want, and it is the right default: a member you forgot to classify is inaccessible rather than exposed.

5. protected, and Why It Needs a Matrix

protected means private, plus classes derived from this one. That definition mentions inheritance, which this curriculum has not taught, so the only honest way to show it is to measure it. Borrowing just enough of Module 16:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Base {
    public:    int pub = 1;
    protected: int pro = 2;
    private:   int pri = 3;
    };

    class Derived : public Base {
    public:
        int readAll() { return pub + pro; }   // both reachable
    };

Measured, each of six combinations compiled separately:

pubpropri
from a derived classOKOKerror: 'pri' is a private member of 'Base'
from outsideOKerror: 'pro' is a protected member of 'Base'error: 'pri' is a private member of 'Base'

The single row that defines protected is the middle cell of the first row: reachable from a derived class, not from outside. private is the only one a derived class cannot reach.

Two notes, then this is handed on.

You will rarely want it. protected data is a weaker guarantee than it looks: every class that derives from yours — including ones written later by other people — can modify it, so the invariant is no longer maintained by one type's code. Chapter 16.4 is titled protected Access and Who It Is Actually For, and that is the right place for the judgement.

And an access specifier can also appear on the base class itself, which changes the access of inherited members rather than of new ones. Measured: class D : private Base {}; makes even pub inaccessible through a D — error: 'pub' is a private member of 'Base'. That is Chapter 16.3's subject.

6. Does Access Control Affect Layout?

It looks like pure compile-time bookkeeping. Measured, three int members, once all public and once split across access sections:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct AllPublic { int a; int b; int c; };
    class  Mixed { public: int a; private: int b; public: int c; };
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
AllPublic: size 12  offsets a=0 b=4 c=8
Mixed    : size 12  offsets a=0 b=4 c=8

Identical. Same size, same offsets, declaration order preserved across the access sections.

That measurement, on its own, would have led me to write "access specifiers do not affect layout" — and that would have been wrong. The compiler said so, three times, while producing it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: 'offsetof' on non-standard-layout type 'Mixed' [-Winvalid-offsetof]

7. What the Warning Means

Mixed is not a standard-layout type, precisely because its members are split across access sections. And for a non-standard-layout type the standard makes no promise about member order between access sections — only within one.

So the honest statement is:

Members declared in one access section are laid out in declaration order. Between access sections, the order is unspecified — this compiler preserved it, and nothing requires that.

Three practical consequences:

For ordinary code, ignore this. You are reading members by name; the compiler knows where they are. Chapter 9.8's padding advice still applies, and declaring the widest members first is still worth doing.

For a type that must match an external layout — a hardware register image, a packet, anything you will memcpy or hand to a C interface — keep all the members in a single access section, and ideally use a struct with no specifiers at all. Chapter 9.8 §9 named protocol and packet layout as Chapter 27.9's subject; this is the Module 11 side of the same constraint.

And offsetof is only valid on a standard-layout type, which is what the warning is literally about. If you need offsetof, you need a type simple enough for it.

This is also a small lesson in method, and the second time this curriculum has hit it: the measurement agreed with a conclusion that is not guaranteed. Chapter 10.3 §5 had the same shape, where new int read as 0 on every attempt and the standard promises nothing. A number that comes out the way you hoped is not the same as a rule, and here the compiler was kind enough to say so unprompted.

8. The Limit, Again

Chapter 11.1 §8 measured it and it belongs in this chapter's own terms: a memcpy through the object's bytes changed a private member from 4 to 100 with no diagnostic at all.

Access control is enforced by the compiler, at compile time, on code that names the member. It is not enforced by the hardware, it leaves no runtime trace, and it does not survive code that bypasses the type system. Chapter 11.1 §8's conclusion stands: it is a mechanism against accident, which is what actually goes wrong.

One facility deliberately named and not taught: friend, which grants a named function or class access to private members. It exists, it is occasionally correct, and reaching for it is usually a sign that the interface needs rethinking — Chapter 11.3 §8 made the same remark.

9. What to Actually Do

Make all data members private in a type with an invariant. The exceptions are not worth finding.

Write class for a type with an invariant and struct for independent data. The keyword is a signal; the private default is also the safer one.

Do not write private: in a struct used as plain data. If you need it, you wanted a class.

Avoid protected data. Every future derived class becomes a place the invariant can break — Chapter 16.4.

Keep all members in one access section when the layout must match something external, and prefer a plain struct there.

Use offsetof only on standard-layout types — the warning is telling you something real.

Ask "what breaks if this is public?" rather than "does anyone need it?".

Prefer a function to a public data member even when they look equivalent: one promises behaviour, the other promises representation.

And do not treat access control as security. Measured: a memcpy broke it silently.

10. Mistakes Worth Making Once

Forgetting that class defaults to private and wondering why nothing compiles. The note says implicitly declared private here.

Thinking class and struct differ in more than the default. Measured identical in size and alignment, both can do everything.

Using protected for data to be helpful to future subclasses. You have given away the invariant.

Expecting protected to mean something without inheritance. It does not; the matrix is the definition.

Concluding from offsetof numbers that mixed access sections preserve order. This compiler did; the standard does not require it, and the warning said so.

Using offsetof on a non-standard-layout type. That is exactly what -Winvalid-offsetof is for.

Making a data member public for convenience. You have promised the representation, not just the value.

Believing private protects against anything but accident. A memcpy went straight through it.

Reaching for friend to resolve an awkward interface.

11. Interview Relevance

"What is the difference between class and struct?" The default access of members — private for class, public for struct — and the default base-class access. Everything else is identical. Candidates who claim a size or performance difference are wrong, and the measurement is the rebuttal.

"What does protected mean?" Accessible to the class and to derived classes, not to outside code. A good answer adds that protected data is usually a mistake, because every derived class can then break the invariant.

"Why make data private?" So the invariant can be maintained by the type's own code. The strong version notes you cannot even take the address of a private member from outside, so the pointer route is closed.

"Can one object of a class access another object's private members?" Yes — access is per-class, not per-object, which is what makes comparison operations writable.

"Do access specifiers affect object layout?" Within a section, declaration order is preserved; between sections the order is unspecified, and mixing them makes the type non-standard-layout. This is a genuinely discriminating question and the -Winvalid-offsetof detail is a strong signal.

12. What You Should Be Able to Say Now

  1. Three specifiers, each applying until the next: public (anywhere), protected (this class and derived), private (this class only).
  2. Measured from outside: public OK, protected and private both errors naming the specifier.
  3. A class's own members can reach all of it, and access is per-class, not per-object — so one Field can read another's private data.
  4. Measured: you cannot even take the address of a private member from outside.
  5. struct defaults to public, class to private — that is the only difference, and the compiler's note says implicitly declared private here.
  6. Everything else is identical: sizeof 8 and alignof 4 for both (Chapter 11.1).
  7. Use the keyword as a signal: struct for independent data, class for a type with an invariant.
  8. protected needs a derived class to mean anything, and the 2×3 matrix is its definition.
  9. protected data gives the invariant away to every future derived class — Chapter 16.4.
  10. Measured: mixed access sections gave identical size and offsets (12, 0/4/8) — and the compiler warned the type is non-standard-layout.
  11. So: order is preserved within a section and unspecified between sections. Keep external layouts in one section.
  12. The measurement agreed with a conclusion that is not guaranteed — the second time in this curriculum.
  13. Access control is compile-time only — a memcpy broke a private member with no diagnostic.
  14. Private is the right default because a wrongly-public member fails silently and a wrongly-private one fails loudly and locally.

13. Exercises

Exercise 1 — fill the matrix. From memory, give the six results for public/protected/private accessed from a derived class and from outside.

Exercise 2 — which keyword? For each, choose struct or class and say why: (a) a 3-D vector (b) a fixed-capacity ring buffer (c) a parsed command-line option set (d) a register field (e) a packet header that must match a wire format.

Exercise 3 — predict. Given class C { int x; public: int y; }; say which compile: (a) c.x from main (b) c.y from main (c) x inside a member of C (d) other.x inside a member of C (e) c.x inside a class derived from C.

Exercise 4 — what breaks? For a RingBuffer with head_, tail_ and data_, answer "what breaks if this is public?" for each member.

Exercise 5 — the layout trap. You must define a type matching a 12-byte hardware register image. Say what you would write, what you would avoid, and why offsetof is relevant.

Exercise 6 — read the warning. Explain what -Winvalid-offsetof on a mixed-access type is actually telling you, and why the identical offsets did not settle the question.

Exercise 7 — the asymmetry. Explain why a wrongly-public member is worse than a wrongly-private one, and connect it to the choice of default.

Exercise 8 — function or member? Field needs its width readable. Compare a public int width_ with a public int width() const, and say what each promises.

14. Summary

  • Three specifiers: public anywhere, protected this class and derived, private this class only — each applying until the next.
  • The class's own code reaches everything, and access is per-class, not per-object.
  • Measured: a private member's address cannot be taken from outside.
  • struct defaults public, class defaults private — the only difference; the note says implicitly.
  • Identical size and alignment; the keyword is a signal to the reader.
  • protected is defined by the matrix and protected data gives away the invariant (Chapter 16.4).
  • Measured: mixed access sections gave identical offsets and a -Winvalid-offsetof warning — order is guaranteed only within a section.
  • Keep external layouts in one access section, and prefer a plain struct.
  • Compile-time only — a memcpy went through it silently.
  • Private is the right default because the opposite mistake is silent and permanent.

The mechanism is now complete: data can be made unreachable, and the only code that may touch it is the type's own. What has not been said is what to do with that capability, and it is entirely possible to use every tool in this chapter and gain nothing — Chapter 11.3's callout described the shape, a class whose data is private and whose setters hand it straight back out, which permits precisely what Chapter 11.1 measured a bare struct permitting. The next chapter is the discipline rather than the mechanism, and it is the module's thesis chapter: how to state an invariant in a form precise enough to check, where the checking has to happen so that no sequence of legal calls can defeat it, and what it means to say an object is valid at every moment a caller could possibly observe it. It is rated Critical, and the reason is that almost every class that goes wrong goes wrong here rather than in the syntax.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C++ Programming curriculum.