Introduction
Hello! In our last lesson, we successfully created our first C++ class, Song, which bundled together data (title and year) and behavior (the display method). However, we made all the members public, allowing any part of our program to directly modify them, like this:
song1.year = -5000; // This is possible, but makes no sense!
This direct access can lead to bugs and objects in invalid states. Today, we'll address this by exploring one of the cornerstones of Object-Oriented Programming: encapsulation.
Encapsulation, also known as information hiding, is the practice of bundling an object's data and methods together while restricting direct access to its internal data. C++ achieves this using access specifiers.
This lesson will teach you how to use the public, private, and protected access specifiers to control who can access your class members. This will allow you to build more robust and secure classes by creating a clear distinction between a class's public interface and its private implementation details.
1. The Public Interface vs. The Private Implementation
In C++, access specifiers define the visibility of class members. The two most common specifiers are public and private.
public: Members declared aspublicform the class's interface. They can be accessed from anywhere—inside the class, by derived classes, and by external code (like yourmainfunction). This is how objects are intended to be used.private: Members declared asprivateare part of the class's implementation. They can only be accessed by other member functions of the same class. This protects the data from accidental or malicious modification from the outside.
By default, all members of a class are private. This is the key difference from a struct, where members are public by default.
Let's start by reading a detailed explanation of public and private members.
14.5 — Public and private members and access specifiers
The article "Public and private members and access specifiers" from Learn C++ is an excellent, in-depth resource. It explains the core concepts, the default access levels for structs and classes, and provides clear code examples.
Please read the following sections: Start from the beginning and read up to (but not including) "Naming your private member variables". This covers the definitions of public and private members. Then, skip to the section "Setting access levels via access specifiers" and read it to see the syntax in action. Focus on the difference in compiler errors when changing a struct to a class.
Now, watch this video for a practical demonstration of how public and private members behave in code.
Public And Private Access Specifiers | C++ Tutorial
The video "Public And Private Access Specifiers" by Portfolio Courses provides a clear, hands-on demonstration of what happens when you try to access public vs. private members from outside the class.
Watch from the beginning until 02:14. Notice how accessing the public name is allowed, but accessing the private salary results in a compiler error.
2. Controlled Access: Getters and Setters
If our data members are private, how do we interact with them? We can't read them or change them from main.
The solution is to provide special public member functions, often called accessors and mutators, or more commonly, getters and setters.
- A getter (accessor) is a public function that returns the value of a private member variable. It provides read-only access.
- A setter (mutator) is a public function that takes a parameter and uses it to modify a private member variable.
Why is this better than making the variable public? Because a setter can validate the incoming data before changing the object's state. For example, a setYear function can ensure the year is within a reasonable range. This enforces data integrity and is a key benefit of encapsulation.
Let's see how to implement these.
Public And Private Access Specifiers | C++ Tutorial
We'll continue with the same video, which now explains how to implement getter and setter functions to provide controlled access to the private salary member.
Watch from 02:14 to 06:10. Pay close attention to how the setSalary function validates the input to prevent negative salaries. This is the power of encapsulation in action.
3. Hands-On: Encapsulating the Song Class
Now it's time to apply these concepts to our Song class from the previous lesson. We'll convert its public data members to private ones and add getters and setters.
Instructions:
- Open the code from your previous exercise.
- Modify the
Songclass:- Change the member variables
titleandyearto beprivate. - Add the
public:access specifier. - Under
public:, declare thedisplay()method as before. - Declare public getter methods:
std::string getTitle() const;andint getYear() const;. (Theconsthere is a promise that these functions won't modify the object's state, which is a best practice for getters. We'll coverconstin more detail later). - Declare public setter methods:
void setTitle(std::string newTitle);andvoid setYear(int newYear);.
- Change the member variables
- Try to compile your
mainfunction. It should fail because it can no longer accesssong.titleandsong.yeardirectly. This is expected! - Implement the getter and setter methods outside the class using the scope resolution operator (
::).- In
setYear(), add validation logic: only setm_yearifnewYearis greater than, say, 1800. Otherwise, you could print an error or set a default value. - Convention Note: It's a common practice to prefix private member variables with
m_(for "member"), as inm_titleandm_year. This helps distinguish them from local variables or parameters. I recommend adopting this convention.
- In
- Update your
mainfunction to use the new getters and setters instead of direct member access.
Here is a revised skeleton to guide you:
#include <iostream>
#include <string>
#include <vector>
class Song {
private:
std::string m_title;
int m_year;
public:
// Setter methods
void setTitle(std::string title);
void setYear(int year);
// Getter methods
std::string getTitle() const;
int getYear() const;
// Other methods
void display() const;
};
// --- Implementation of methods ---
void Song::setTitle(std::string title) {
m_title = title;
}
void Song::setYear(int year) {
if (year > 1800) {
m_year = year;
} else {
m_year = 0; // Or handle the error appropriately
std::cout << "Error: Invalid year for " << m_title << std::endl;
}
}
std::string Song::getTitle() const {
return m_title;
}
int Song::getYear() const {
return m_year;
}
void Song::display() const {
std::cout << "Title: " << m_title << ", Year: " << m_year << std::endl;
}
int main() {
std::vector<Song> playlist;
Song song1;
song1.setTitle("Stairway to Heaven");
song1.setYear(1971);
playlist.push_back(song1);
Song song2;
song2.setTitle("Bohemian Rhapsody");
song2.setYear(1975);
playlist.push_back(song2);
Song song3;
song3.setTitle("A Song from the Future");
song3.setYear(1700); // This should trigger your validation
playlist.push_back(song3);
std::cout << "--- My Playlist ---\n";
for (const auto& song : playlist) {
song.display();
}
return 0;
}
4. The protected Specifier
There is a third access specifier, protected, which is specifically related to inheritance (a topic we'll cover in the next module).
protected: Members declared asprotectedare inaccessible from outside the class, just likeprivatemembers. However, they can be accessed by member functions of derived classes (child classes).
Think of it as a middle ground: private is for "secrets of this class only," while protected is for "secrets to be shared within the family (class hierarchy)." Since your goal includes building complex desktop applications, understanding this is important for creating reusable components.
This short video gives a concise explanation. Don't worry about understanding inheritance fully yet; just focus on the accessibility rule.
Protected Class Members | C++ Tutorial
The video "Protected Class Members" by Portfolio Courses explains the specific use case for the protected specifier in the context of inheritance.
Watch from the beginning to 04:32. The key takeaway is seeing how a derived class can access the protected member of its base class, but the main function cannot.
Summary of Access Specifiers
To tie everything together, here is a clear summary of the rules for each access specifier.

Here are the best practices in a nutshell:
- Data members should be
private. This is the essence of encapsulation. - The public interface should be
public. These are the functions that users of your class will call. - Use
protectedfor members that are implementation details of your base class but need to be accessible to derived classes for customization. - Use
structfor simple data aggregates that don't need encapsulation (all memberspublic). Useclasswhen you need to enforce invariants and hide implementation details.
Conclusion
Great job! You have now mastered a fundamental concept of object-oriented design in C++. By using access specifiers, you can create robust, maintainable, and secure classes that protect their own data.
Key Takeaways:
- Encapsulation is about hiding a class's internal implementation and providing a public interface.
publicmembers are accessible from anywhere and form the class's interface.privatemembers are only accessible by other members of the same class, protecting the object's state.protectedmembers are likeprivatebut are also accessible to derived classes.- Getters and Setters are public methods that provide controlled read/write access to private data, often including validation logic.
- Classes default to
privateaccess; structs default topublic.
Next Up
In our hands-on exercise, we created a Song object and then used setter methods to populate its data. This is a bit clunky. Wouldn't it be better if we could initialize the object with its data all in one step, right when it's created?
In the next lesson, we will learn about special member functions called constructors, which do exactly that. We will also learn about destructors, which handle cleanup when an object is destroyed.