Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Controlling Member Access
Hide implementation details with private members and expose safe interfaces with public ones.
Public and Private Members and Access Specifiers
Classes let you control who can access their members using access specifiers.
The three access specifiers
C++ provides three access levels:
- public: Accessible from anywhere
- private: Accessible only within the class
- protected: Accessible within the class and derived classes (covered later)
Public members
Public members are accessible to everyone:
class Character
{
public:
int health{};
void takeDamage(int amount)
{
health -= amount;
}
};
int main()
{
Character hero{};
hero.health = 100; // OK: public member variable
hero.takeDamage(25); // OK: public member function
return 0;
}
Anyone with access to a Character object can read or modify health and call takeDamage().
Private members
Private members are accessible only within the class itself:
class Wallet
{
private:
int gold{};
public:
void addGold(int amount)
{
gold += amount; // OK: inside the class
}
int getGold()
{
return gold; // OK: inside the class
}
};
int main()
{
Wallet playerWallet{};
playerWallet.addGold(100); // OK: public function
std::cout << playerWallet.getGold(); // OK: public function
playerWallet.gold = 9999999; // Error: gold is private!
return 0;
}
Direct access to gold from outside the class is forbidden. You must use the public interface (addGold(), getGold()).
Default access levels
In classes: Members are private by default.
class Example
{
int privateByDefault{}; // private
public:
int explicitlyPublic{}; // public
};
In structs: Members are public by default.
struct Data
{
int publicByDefault{}; // public
private:
int explicitlyPrivate{}; // private
};
Be explicit with access specifiers for clarity.
Why use private members?
Data protection: Prevent invalid states.
class Gauge
{
private:
int value{}; // Always 0-100
public:
void setValue(int v)
{
if (v >= 0 && v <= 100)
value = v;
}
int getValue() const
{
return value;
}
};
Making value private ensures it can't be set outside the 0-100 range. The setValue() function validates input.
Implementation hiding: Change internals without breaking code that uses the class.
// Version 1
class Cache
{
private:
int data[50]{};
int count{};
public:
void store(int value);
int retrieve(int index);
};
// Version 2 - changed implementation
class Cache
{
private:
std::vector<int> data{}; // Changed to vector
public:
void store(int value); // Interface unchanged!
int retrieve(int index);
};
Users of Cache don't need to change their code when you change the internal implementation.
Controlled access: Perform actions when values change.
class Monitor
{
private:
int failureCount{};
public:
void recordFailure()
{
++failureCount;
if (failureCount > 10)
triggerAlarm();
}
int getFailureCount() const
{
return failureCount;
}
private:
void triggerAlarm();
};
By controlling access to failureCount, you can trigger alarms when failures exceed a threshold.
Naming conventions
Many teams prefix private members with m_ (member) or append _:
class Example
{
private:
int m_health{}; // Prefix style
double damage_{}; // Suffix style
public:
int getHealth() const { return m_health; }
void setHealth(int h) { m_health = h; }
};
Choose a convention and apply it consistently.
Member access in practice
class Weapon
{
private:
int damage{};
int durability{};
public:
// Setters validate input
void setDamage(int d)
{
if (d > 0)
damage = d;
}
void setDurability(int dur)
{
if (dur >= 0)
durability = dur;
}
// Getters provide read access
int getDamage() const { return damage; }
int getDurability() const { return durability; }
// Computed properties
int criticalDamage() const
{
return damage * 2;
}
bool isBroken() const
{
return durability == 0;
}
};
This design:
- Protects data from invalid values
- Provides a clean public interface
- Can change implementation without affecting users
- Computes derived values on demand
Keep data members private and expose them through public getters/setters only when needed. Make the interface minimal by only exposing what users need. Validate in setters to check that values are valid before storing them. Use const for getters since they typically don't modify the object.
Summary
Access specifiers: C++ provides three access levels - public (accessible from anywhere), private (accessible only within the class), and protected (accessible within the class and derived classes).
Default access levels: In classes, members are private by default. In structs, members are public by default. Being explicit with access specifiers improves code clarity.
Data protection: Private members prevent invalid states by controlling access through public member functions that can validate input and enforce invariants.
Implementation hiding: Private members allow you to change internal implementation without breaking code that uses the class, as long as the public interface remains the same.
Controlled access: Using private members with public access functions lets you perform additional actions when values change, such as validation, logging, or triggering side effects.
Access specifiers are fundamental to encapsulation, one of the core principles of object-oriented programming. By controlling member visibility, you create clean public interfaces while protecting implementation details and maintaining object integrity.
Controlling Member Access - Quiz
Test your understanding of the lesson.
Practice Exercises
Access Specifiers
Use public and private access specifiers to control access to class members.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!