Skip to content
VLSI Mentor

C++ Part I · Module 16 · Inheritance

protected Access and Who It Is Actually For

Chapter 16.2 measured the access table and treated it as a fact about syntax: a derived class can reach its base's public and protected members and not its private ones. That table hides a design decision, and this chapter is about the decision rather than the rule. The word protected is usually introduced as a middle setting between public and private, which makes it sound like a careful compromise — and for a member function it genuinely is one, because it names an operation you are deliberately offering to derived classes and to nobody else. For a data member it is nothing of the kind. A protected field is writable by every class that derives from yours, which means every class that will ever derive from yours, written by people who have not read your invariant and are under no obligation to maintain it. There is no approval step, no interface to pass through, and no way for the base to notice. This chapter measures what that costs on a buffer whose only invariant is that the used count never exceeds the capacity: a derived class of three lines, containing nothing the compiler could possibly object to, leaves the invariant false. The same base with the field made private and the corresponding operation made protected instead refuses the identical derived class — not by convention or review, but because the assignment it wanted to write does not compile.

Intermediate17 min readC++Part IFundamentalsInheritance

Part I · Module 16 · Chapter 16.4 · protected Access and Who It Is Actually For

1. The Rule, and the Question It Hides

From Chapter 16.2 §7, measured:

In the basePublic codeDerived member functions
publicyesyes
protectednoyes
privatenono
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'priv' is a private member of 'Base'      (derived code refused)
'prot' is a protected member of 'Base'    (outside code refused)

So protected is the row that exists for inheritance. The rule is simple and the question it hides is not:

Who, exactly, is "derived member functions"?

The answer is not "the derived class I am about to write". It is every class that will ever derive from this one, including ones written next year by people who will never read your class. §4 is what that costs.

2. The Invariant Under Test

A buffer whose single invariant is that the used count never exceeds the capacity:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct BufferProtected {
        explicit BufferProtected(int cap) : capacity_(cap), used_(0) {}
        bool invariantHolds() const { return used_ >= 0 && used_ <= capacity_; }
        int used() const { return used_; }
    protected:
        int capacity_;
        int used_;                 // PROTECTED data
    };

Chapter 11.5 §2 required an invariant to hold for the object's whole lifetime, and Chapter 11.4 established that private is how you make that enforceable. protected is being used here exactly as people habitually use it: the derived classes will need these, so open them up a little.

3. The Derived Class That Breaks It

Written months later, by someone with a deadline:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct GreedyProtected : BufferProtected {
        using BufferProtected::BufferProtected;
        void grabEverything() { used_ = 999999; }          // compiles
    };
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A. protected DATA member:
     used=999999  invariant holds? 0   <-- base invariant BROKEN by a derived class

The invariant is false and the compiler had no objection. used_ = 999999; is an ordinary assignment to an ordinary accessible field. There is no cast, no trick, and nothing a reviewer looking at GreedyProtected alone would flag — the line is only wrong if you know what BufferProtected promises, and that knowledge is in a different file.

Note what the base class can do about this: nothing. It cannot validate, cannot log, cannot even detect it. The field was handed over.

4. Why protected Data Is a Wider public

The usual mental model puts protected near private because of where it sits in the list. The measurement puts it near public:

A protected data member can be written by anyone willing to write : YourClass. That is not a restriction, it is a formality.

Three properties make the exposure worse than it looks.

The set of writers is open, and it propagates. A public member has a knowable set of users today; so does a protected one — but the set of derived classes grows without the base author being involved, and each new one gets full write access by declaration. Access also passes straight down a chain. Measured, with two intermediate classes that touch nothing at all:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct Layer1 : Buffer { using Buffer::Buffer; };     // touches nothing
    struct Layer2 : Layer1 { using Layer1::Layer1; };     // touches nothing
    struct Layer3 : Layer2 { void grab() { used_ = 999999; } };
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
three derivations below the base that declared it protected:
     Layer3 wrote used_ = 999999 -> used=999999  invariant holds? 0

Three levels down, and the field is still writable. protected does not mean "my immediate subclasses"; it means every descendant, at any depth, forever. A base author reviewing Layer1 and Layer2 would see nothing to object to — and Layer3 need not have been written yet.

There is no interface to maintain. With a public member function you can change the implementation freely. With a protected field you have published the representation: its name, its type, and its meaning. Changing int used_ to a std::size_t, or splitting it across two fields, breaks every derived class. Chapter 11.5 §5 made this argument as operations, not setters; it applies identically here, with a smaller audience and the same force.

And the invariant loses its single owner. This is the real cost. Chapter 11.5's whole claim is that a class owns its invariant — one place that may modify the state, so one place to get right. A protected field distributes that responsibility across every derived class, which means the invariant is now maintained by convention among several authors, which is the arrangement Chapter 10.9 §6 measured being unreliable for ownership.

5. The Same Base, Done Properly

Keep the data private. Offer the operation the derived class actually needs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct BufferPrivate {
        explicit BufferPrivate(int cap) : capacity_(cap), used_(0) {}
        bool invariantHolds() const { return used_ >= 0 && used_ <= capacity_; }
        int used() const { return used_; }
    protected:
        // the derived class gets an OPERATION, not the field
        bool take(int n) { if (n < 0 || used_ + n > capacity_) return false; used_ += n; return true; }
    private:
        int capacity_;
        int used_;
    };

The identical derived intent now has to go through take:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct GreedyPrivate : BufferPrivate {
        using BufferPrivate::BufferPrivate;
        bool grabEverything() { return take(999999); }
    };
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
B. private data + protected OPERATION:
     take() returned 0, used=0  invariant holds? 1

take refused, used_ stayed 0, and the invariant holds. The derived class could not have broken it even deliberately, because the only route to the state is a function that checks.

And had it tried the direct route, it would not compile — Chapter 16.2 §7's measurement: 'used_' is a private member. The protection is mechanical rather than advisory.

6. The Two Words Are Not Alternatives

The distinction that matters, and the one the chapter title is about:

Protected dataProtected function
what you publishthe representationan operation
who maintains the invariantevery derived classthe base
cost of changing itbreaks derived classesfree
can the base validate?noyes
verdicta smellthe tool

So this is not an argument against protected. It is an argument about what you put behind it. protected on an operation is the feature; protected on a field is the leak.

7. What protected Is Actually For

Three legitimate uses, in descending order of frequency.

An operation only derived classes should perform. §5's take is the shape: the base owns the state and the invariant, and offers a checked way for derived classes to change it. Everything a derived class genuinely needs can be expressed this way.

A constructor that only derived classes should call, which is how you make a base class usable only as a base without making it abstract. Chapter 12.5 §9 noted explicit and = delete as complementary controls; a protected constructor is the inheritance-shaped member of that family.

And a customisation point for derived classes to implement. A base class that defines the sequence of a process and leaves individual steps to derived classes puts those steps behind protected — they are part of the derivation contract, not of the public interface. (This shape depends on virtual, which is Chapter 17.2's subject, and designing such an interface properly is Chapter 17.9's. The access decision is the part that belongs here: the steps are protected because only derived classes implement them, while the public interface stays small.)

A useful test before writing protected: can I state, in one sentence, the operation a derived class needs? If yes, that sentence is a protected member function. If the only answer is "it needs the field", you have not yet worked out what it is doing with it.

8. One Honest Case for a protected Field

Being fair to the other side: there is a narrow case where a protected field is defensible, and recognising it keeps the rule from becoming dogma.

A base class with no invariant over that field. If a field is genuinely just storage — no relationship to any other member, no range constraint, nothing the base promises about it — then there is no invariant to break, and a protected field costs only the representation coupling from §4.

The honest examples are thin: a protected buffer pointer in a tightly-coupled intrusive data structure, or a tag field in a hierarchy whose base deliberately promises nothing. Even then the coupling remains — change the type and every derived class recompiles at best.

And the test for this case is the one from §4: can the base still change the field's type, name, or representation without breaking derived classes? If not — and it usually cannot — then the field is part of your published interface whether you meant it to be or not.

9. What to Actually Do

Keep data private. Always, in a base class included.

Put the operation behind protected, checked, so the base keeps its invariant.

State what a derived class needs to do in one sentence — that sentence is the protected member function.

Use a protected constructor to make a class usable only as a base.

Treat a protected field as published representation — because changing it breaks every derived class.

Remember the writers are an open set — including classes written after yours.

And if a field truly has no invariant, note that you still pay the coupling, and prefer a protected accessor anyway.

10. Mistakes Worth Making Once

A protected data member in a class with an invariant. Measured: used=999999, invariant holds? 0, zero warnings.

Thinking protected is close to private. The set of writers is unbounded and grows without you.

Expecting the base to detect the damage. It cannot — the field was handed over.

Changing a protected field's type later. Every derived class breaks; it was published.

Reaching for protected because the derived class "needs the data". You have not yet said what it needs to do.

Concluding protected is never useful. A protected operation is the correct tool — measured refusing an illegal request.

Making the whole base interface protected so derived classes can reach it. That is a base class with no public interface, which Chapter 16.8 §4 treats as a symptom.

Assuming a reviewer will catch it. The offending line is only wrong in the context of another file.

11. Interview Relevance

"What is protected for?" Members that derived classes need and outside code must not have. The strong answer immediately distinguishes a protected operation from a protected field.

"Why is a protected data member a smell?" Because it publishes the representation to an unbounded, growing set of writers and distributes the invariant. Measured: a three-line derived class set used_ to 999999 with no diagnostic.

"Is protected closer to public or private?" In effect, public — you cannot enumerate who will write to it.

"How do you give a derived class what it needs without the field?" A checked protected member function. Measured: it refused the illegal request and left the invariant intact.

"When is a protected field defensible?" When the base has no invariant over it — and even then you have published its type and name.

"What else is protected good for?" A constructor, making a class usable only as a base; and customisation points derived classes implement.

12. What You Should Be Able to Say Now

  1. protected means accessible to derived classes and to nobody else — measured both refusals.
  2. "Derived classes" means every class that will ever derive, including ones written later by other people.
  3. Measured: a three-line derived class set a protected used_ to 999999, leaving invariantHolds() false, with zero warnings.
  4. The base can do nothing about it — it cannot validate, log, or detect the write.
  5. Measured: with the field private and a checked take() protected instead, the same intent returned 0 and left used_ at 0, invariant intact.
  6. So protected on data behaves like public — an open, growing set of writers with no interface in between.
  7. A protected field publishes the representation — name, type and meaning — so changing it breaks derived classes.
  8. And it distributes the invariant, removing the single owner Chapter 11.5 relies on.
  9. A protected function keeps the invariant with the base while still giving derived classes what they need.
  10. The rule is publish verbs, not nouns.
  11. The test: can you state in one sentence what the derived class needs to do? That sentence is the function.
  12. A protected constructor makes a class usable only as a base.
  13. Customisation points belong behind protected, keeping the public interface small.
  14. A protected field is defensible only where the base has no invariant over it — and the coupling remains.
  15. No reviewer reading the derived class alone can catch this — the line is wrong only in another file's context.

13. Exercises

Exercise 1 — the access table. Fill in all three rows for public code and derived code, with the diagnostic for each refusal.

Exercise 2 — who can write it. For a protected int in a base class, describe the complete set of code that may assign to it.

Exercise 3 — break it. Given a base whose invariant is head_ <= tail_, write the shortest derived class that falsifies it, and say what warns.

Exercise 4 — fix it. Rewrite that base with private data and a protected operation, then show the same derived class being refused.

Exercise 5 — verbs not nouns. Three derived classes "need" a protected count_. For each, state what they need to do and give the member function.

Exercise 6 — the constructor. Make a class that can be derived from but not instantiated directly, without making it abstract.

Exercise 7 — the honest case. Describe a field for which protected is defensible, and say what you still give up.

Exercise 8 — publication. A base changes protected int used_ to protected std::size_t used_. List what breaks and why that makes the field part of the interface.

14. Summary

  • protected means derived classes and nobody else — and "derived classes" is an open set that grows without you.
  • Measured: a three-line derived class assigned to a protected field and left the base's invariant false, with zero warnings and nothing the base could do.
  • Measured: the same base with private data and a checked protected take() refused the identical request and kept the invariant.
  • So protected data behaves like public data with a longer approval process: an unbounded audience and no interface in between.
  • It publishes the representation and distributes the invariant, removing the single owner Chapter 11.5 depends on.
  • A protected operation is the correct tool — the base keeps the invariant and still serves its derived classes.
  • Publish verbs, not nouns, and use §7's one-sentence test before writing the keyword.
  • A protected constructor is the other legitimate use; a protected field is defensible only where no invariant exists over it.

Access settled, the next question is the one Chapter 16.2's picture made inevitable. A derived object is one object with two parts, and the base part has to be a valid object before the derived part can rely on it — which means construction has an order, and so does destruction. The next chapter measures both, and the result is more structured than "base first": with members in the base and members in the derived class, four distinct phases run in a fixed sequence, and tearing down runs all four in exact reverse. The chapter also measures the diagnostic you get when a derived constructor fails to initialise a base that has no default constructor, which is one of the clearer error messages in C++ and worth recognising on sight — and one consequence of the ordering that catches people out, which is that for part of a derived object's construction the derived part does not exist yet.

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.