Protected member variables are similar to global variables; any derived class can modify them. When protected member variables are used, invariants
cannot be enforced. Also, protected member variables are hard to maintain since they can be manipulated through multiple classes in different
files.
If a class is just a data store without logic, it can safely contain only public
member variables and no member functions. Otherwise,
data members are tightly coupled to the class logic, and encapsulation must be used. In this case, having only private member variables enforces
invariants for data and ensures that logic is defined only in the member functions of the class. Structuring it this way makes it easier to guarantee
integrity and easier for maintainers to understand the code.
Using protected
member variables breaks the encapsulation. The risk is that data integrity logic spreads through the class and all its
derived classes, becoming a source of complexity that will be error-prone for maintainers and extenders.
That is why protected
member variables should be changed to private
and manipulated exclusively through
public
or protected
member functions of the base class.
This rule raises an issue when a class
or struct
contains protected
member variables.
Noncompliant code example
class Stat {
public:
long int getCount() {
return count;
}
protected:
long int count = 0; // Noncompliant; expose a protected member variable.
// By just looking at "Stat" class, it's not possible to be sure that "count"
// is modified properly, we also need to check all derived classes
};
class EventStat : public Stat {
public:
void onEvent() {
if (count < LONG_MAX) {
count++;
}
}
};
Compliant solution
class Stat {
public:
long int getCount() {
return count;
}
protected:
void increment() { // Compliant; expose a protected member function
if (count < LONG_MAX) {
count++;
}
}
private:
long int count = 0; // member variable is private
};
class EventStat : public Stat {
public:
void onEvent() {
increment();
}
};
Exceptions
Const member variables and reference member variables are ignored since they don’t break invariants.