Access Modifiers
Control access to class members with public, private, and protected modifiers to enforce encapsulation
Master the three access modifiers in C++ to control who can access class members and protect your data from invalid modifications.
A Simple Example
#include <iostream>
#include <string>
class Player {
private:
int health;
int maxHealth;
std::string secretCode;
protected:
int experiencePoints;
public:
std::string name;
Player(std::string playerName, int hp)
: name{playerName}, health{hp}, maxHealth{hp}, experiencePoints{0}, secretCode{"XYZ123"} {
}
void heal(int amount) {
if (amount > 0) {
health += amount;
if (health > maxHealth) health = maxHealth;
std::cout << "Healed " << amount << " HP\n";
} else {
std::cout << "Invalid heal amount" << "\n";
}
}
bool takeDamage(int amount) {
if (amount <= 0) {
std::cout << "Invalid damage amount" << "\n";
return false;
}
health -= amount;
if (health < 0) health = 0;
std::cout << "Took " << amount << " damage\n";
return health > 0;
}
int getHealth() const {
return health;
}
};
int main() {
Player player{"Alice", 100};
player.name = "Alice the Brave"; // OK - public
player.heal(20); // OK - public method
std::cout << "Current health: " << player.getHealth() << "\n";
// player.health = 999999; // ERROR - health is private!
// player.secretCode = "0"; // ERROR - secretCode is private!
return 0;
}
Breaking It Down
private - Internal Implementation
- What it does: Only accessible within the class itself - completely hidden from outside
- Default for classes: If you don't specify, members are private by default
- Use for: Internal data and helper methods that should never be accessed directly
- Remember: Private means "no one else's business" - protects invariants and prevents misuse
public - External Interface
- What it does: Accessible from anywhere - inside or outside the class
- Use for: Methods that form the public API, constructors, and occasionally data
- Guideline: Keep data private, make methods public
- Remember: Public members are your contract with users - changing them breaks code
protected - Inheritance Access
- What it does: Accessible within the class and its derived classes
- Use for: Data and methods that subclasses need but external code shouldn't access
- Common scenario: Shared state or utility functions for a class hierarchy
- Remember: Protected is for inheritance - if you're not using inheritance, stick to private and public
Encapsulation - The Golden Rule
- What it is: Hide internal details, expose only necessary functionality
- Pattern: Keep data private, provide public getter/setter methods
- Benefit: Change internal implementation without breaking external code
- Remember: Getters should be const methods since they don't modify the object
Why This Matters
- Imagine building a car where anyone can reach in and change the engine temperature or fuel level directly. That's what happens without access modifiers - chaos and bugs.
- Access modifiers are the locks and keys of object-oriented programming. They protect your data from invalid modifications and let you change internal implementation without breaking code that uses your class.
- This is fundamental to writing professional, maintainable software. Master access modifiers and you control the boundaries of your objects.
Critical Insight
Keep data private, provide public methods. This is encapsulation's golden rule. Why? Because you can change the internal representation without breaking code that uses your class. Want to change how health is stored internally? Easy - just update the implementation. External code never notices!
// Version 1: Health as absolute value
class Player {
private:
int health;
int maxHealth;
public:
int getHealth() const { return health; }
double getHealthPercent() const { return 100.0 * health / maxHealth; }
};
// Version 2: Health as percentage (changed implementation!)
class Player {
private:
double healthPercent; // now stored as 0.0 to 1.0
public:
int getHealth() const { return static_cast<int>(healthPercent * 100); }
double getHealthPercent() const { return healthPercent * 100; }
};
// External code doesn't need to change!
Best Practices
Make data members private by default: Protect your internal state and provide controlled access through public methods.
Use getters and setters for validation: Never expose data directly - provide methods that can validate input and maintain invariants.
Mark getters as const: Since getters don't modify the object, declare them with const to prevent accidental changes.
Keep the public interface minimal: Every public member is a commitment. Make only what's necessary public to reduce coupling.
Common Mistakes
Making everything public: New programmers often make all members public for convenience, defeating the purpose of encapsulation.
Direct access to private members: Trying to access private members from outside the class causes compilation errors. Use public methods instead.
Forgetting const on getters: Getters should be const methods since they don't modify the object. Without const, they can't be called on const objects.
struct vs class default: Structs default to public, classes default to private. Be explicit about access modifiers to avoid confusion.
Debug Challenge
This class exposes the health publicly. Click the highlighted line to fix the access modifier:
Quick Quiz
- What is the default access level for class members?
- Which access modifier should you use for data that needs validation before being changed?
- Can a private method call a public method in the same class?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once