A protected member is accessible to its class and derived classes, but not ordinary client code. That convenience can weaken encapsulation: derived classes may bypass validation that preserves the base class invariant. section 8.4
Treating protected data as a shortcut creates a maintenance trap. The base class contains the rules for valid state, while every derived class must rediscover and correctly apply those rules; the compiler cannot enforce that obligation. section 8.4
Prefer private state with controlled operations because it:
- Centralizes validation in constructors and mutators. section 8.4
- Prevents derived classes from silently creating out-of-range state. section 8.4
- Still lets derived classes use inherited accessors and mutators. section 8.4
The negative-age mistake
A Student derived from Person can implement setAge by assigning directly to protected age. Calling jane.setAge(-26) then bypasses Person's constructor validation and leaves the object with an invalid age. Making age private and routing construction through Person::setAge preserves the invariant. section 8.4
Listing 8.6 Vulnerability of the protected instance variable
class Person {
protected:
std::string name;
int age; #1
public:
Person(const std::string& name, int age) : name(name) {
if (age < 0) #2
throw std::invalid_argument("negative age");
this->age = age;
}
std::string getName() const { return name; }
int getAge() const { return age; }
};
class Student : public Person {
private:
double gpa;
public:
Student(const std::string& name, int age, double gpa) : Person
(name, age), gpa(gpa) {}
double getGpa() const { return gpa; }
void setAge(int age) { this->age = age; } #3
};
int main() {
Student jane("Jane", 26, 3.85);
jane.setAge(-26);
std::cout << "Jane is " << jane.getAge() << " years old\n";
return 0;
}
Shows how a derived class directly mutates a protected age member and bypasses validation. section 8.4
Listing 8.7 Enhancing safety by making instance variables private
class Person {
private:
std::string name;
int age; #1
public:
Person(std::string name, int age) : name(name) { setAge(age); }
std::string getName() const { return name; }
int getAge() const { return age; }
void setAge(int age) {
if (age < 0) #2
throw std::invalid_argument("negative age");
this->age = age;
}
};
class Student : public Person {
private:
double gpa;
public:
Student(std::string name, int age, double gpa) :
Person(name, age), gpa(gpa) {} #3
double getGpa() const { return gpa; }
};
int main() {
Student jane("Jane", 26, 3.85);
jane.setAge(-26);
std::cout << "Jane is " << jane.getAge() << " years old\n";
return 0;
}
Shows the safer private-member design, with validation centralized in setAge. section 8.4



