Encapsulation and Access Modifiers in C++ (public, protected, private)
Learn encapsulation and access modifiers in C++ using public, protected, and private to build secure and maintainable classes.
Encapsulation is one of the four fundamental concepts of Object-Oriented Programming (OOP). It means hiding the data (attributes) of a class from the outside world and allowing access to it only through well-defined methods. This approach preserves data integrity and makes the internal implementation of a class independent from external usage.
1) What Is Encapsulation?
Encapsulation is the principle of “data hiding.” The internal details of a class (attributes, helper methods) are hidden from the outside. The user interacts only through the public interface of the class.
Advantages of encapsulation:
- Data is protected against misuse or corruption.
- The internal logic of the class can change without affecting external code.
- It improves security, maintainability, and testability.
2) Access Modifiers
C++ provides three types of access modifiers:
| Modifier | Access Level | Usage |
|---|---|---|
public |
Accessible from anywhere | Defines the class interface (public API) |
protected |
Accessible only within the class and derived classes | Shared functionality used in inheritance |
private |
Accessible only within the class | Used for data members and helper functions |
3) Encapsulation Example with a Book Class
Below is an example of a Book class that demonstrates encapsulation.
#include <iostream>
#include <string>
using namespace std;
class Book {
private:
string title;
string author;
double price;
int pageCount;
public:
// Constructor
Book(string t, string a, double p, int pc)
: title(t), author(a), price(p), pageCount(pc) {}
// Getters and Setters
string GetTitle() const { return title; }
string GetAuthor() const { return author; }
double GetPrice() const { return price; }
void SetPrice(double p) {
if (p >= 0) price = p;
else cout << "Price cannot be negative!" << endl;
}
int GetPageCount() const { return pageCount; }
void Print() const {
cout << "Book: " << title
<< " | Author: " << author
<< " | Pages: " << pageCount
<< " | Price: " << price << " TL" << endl;
}
};
int main() {
Book b("C++ Primer", "Lippman", 180.0, 950);
b.Print();
b.SetPrice(-50); // Invalid
b.SetPrice(210);
b.Print();
}
The private fields cannot be modified directly.
Data modification must occur through controlled methods such as SetPrice().
4) Protected Members and Inheritance
Derived classes can access protected members directly.
This allows subclasses to extend or reuse data from the base class.
class EBook : public Book {
protected:
string format; // Example: PDF, EPUB
public:
EBook(string t, string a, double p, int pc, string f)
: Book(t, a, p, pc), format(f) {}
void PrintWithFormat() const {
cout << GetTitle() << " (" << format << ")" << endl;
}
};
Here, the derived class adds a new feature (format) without breaking encapsulation.
5) The friend Keyword
Normally, private members can only be accessed within the class itself.
However, sometimes another class or function may need access to this private data.
In such cases, a friend declaration can be used.
class Book {
private:
string title;
double price;
public:
Book(string t, double p) : title(t), price(p) {}
// friend function declaration
friend void ApplyDiscount(Book& b, double rate);
};
void ApplyDiscount(Book& b, double rate) {
b.price *= (1 - rate); // access to private field
}
int main() {
Book b("Design Patterns", 300);
ApplyDiscount(b, 0.1);
}
The use of friend should be handled carefully since it breaks encapsulation.
It should only be used when necessary (e.g., operator overloading, debugging tools).
6) Getter and Setter Design Principles
- Use inline getters (
const) for simple data access. - Always include validation within setters.
- Make fields readonly (getter only) when modification is not allowed.
- Instead of defining unnecessary getters/setters, abstract behavior directly through methods.
class Book {
private:
string title;
double price;
public:
Book(string t, double p) : title(t), price(p) {}
string GetTitle() const { return title; } // read-only
void ApplyDiscount(double rate) { price *= (1 - rate); } // behavior instead of setter
};
7) Data Safety with the const Keyword
const methods do not modify the state of the class.
This supports encapsulation and prevents unexpected modifications.
class SafeBook {
private:
string title;
public:
SafeBook(string t) : title(t) {}
string GetTitle() const { return title; } // const method
void SetTitle(string t) { title = t; }
};
Writing a Get method without const violates this principle and can lead to unexpected bugs.
8) Immutable Object Pattern
Following the encapsulation principle, some objects are designed to be “immutable.”
All their members are declared const and they do not provide setter methods.
class ImmutableBook {
private:
const string title;
const string author;
const int pages;
public:
ImmutableBook(string t, string a, int p)
: title(t), author(a), pages(p) {}
string GetTitle() const { return title; }
string GetAuthor() const { return author; }
int GetPages() const { return pages; }
};
This approach ensures data integrity, especially in multi-threaded environments.
9) Encapsulation and Object State Management
Encapsulation not only controls access but also ensures that an object remains in a valid state. The internal state of the class should always maintain valid values.
class ValidatedBook {
private:
double price;
public:
void SetPrice(double p) {
if (p < 0) throw invalid_argument("Price cannot be negative.");
price = p;
}
};
Such validation mechanisms ensure that the class maintains a “valid state” and prevent invalid data from being passed by external code.
10) TL;DR
- Encapsulation means hiding data and exposing behavior through controlled access.
private: accessible only within the class.protected: accessible within the class and derived classes.public: accessible everywhere (class interface).friendallows external access to private data (use with caution).- Validation and
constusage are crucial in getters/setters. - Immutable objects enhance data safety.
- Encapsulation is essential for maintainable and error-free designs.
- All examples can be run on Visual Studio 2022 or GCC 11+.