Access specifiers are keywords in C++ that determine the visibility and accessibility of class members (variables and methods). They play a crucial role in implementing encapsulation, one of the fundamental principles of object-oriented programming.
The Three Access Specifiers
C++ provides three access specifiers:
- public: Members can be accessed from anywhere
- protected: Members can be accessed within the class and by derived classes
- private: Members can only be accessed within the class itself
Syntax and Usage
Access specifiers appear in a class definition followed by a colon:
class ClassName {
public:
// Public members go here
protected:
// Protected members go here
private:
// Private members go here
};
Understanding Each Access Specifier
Public Access Specifier
Public members are accessible from:
- Inside the class
- Derived classes
- Outside the class (any code with access to an object of the class)
#include <iostream>
using namespace std;
class Person {
public:
string name;
void displayName() {
cout << "Name: " << name << endl;
}
};
int main() {
Person person;
person.name = "John"; // OK, public member
person.displayName(); // OK, public method
return 0;
}
Protected Access Specifier
Protected members are accessible from:
- Inside the class
- Derived classes
- NOT accessible from outside the class
#include <iostream>
using namespace std;
class Person {
protected:
string name;
public:
Person(string n) : name(n) {}
void displayName() {
cout << "Name: " << name << endl;
}
};
class Student : public Person {
private:
int studentId;
public:
Student(string n, int id) : Person(n), studentId(id) {}
void display() {
cout << "Student ID: " << studentId << endl;
cout << "Name: " << name << endl; // Can access protected member from base class
}
};
int main() {
Person person("John");
// person.name = "David"; // Error: protected member not accessible here
person.displayName(); // OK, public method
Student student("Alice", 12345);
// student.name = "Bob"; // Error: protected member not accessible here
student.display(); // OK, public method
return 0;
}
Private Access Specifier
Private members are accessible from:
- Inside the class only
- NOT accessible from derived classes
- NOT accessible from outside the class
#include <iostream>
using namespace std;
class BankAccount {
private:
double balance;
string accountNumber;
public:
BankAccount(string accNum, double initialBalance)
: accountNumber(accNum), balance(initialBalance) {}
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
bool withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
return true;
}
return false;
}
double getBalance() {
return balance;
}
string getAccountNumber() {
return accountNumber;
}
};
int main() {
BankAccount account("AC12345", 1000.0);
// account.balance = 5000.0; // Error: private member not accessible
// account.accountNumber = "AC56789"; // Error: private member not accessible
account.deposit(500.0);
cout << "Account: " << account.getAccountNumber() << endl;
cout << "Balance: $" << account.getBalance() << endl;
if (account.withdraw(200.0)) {
cout << "Withdrawal successful" << endl;
cout << "New balance: $" << account.getBalance() << endl;
} else {
cout << "Withdrawal failed" << endl;
}
return 0;
}
Default Access Specifier
The default access specifier depends on the type of user-defined type:
- For
class: The default access specifier isprivate - For
struct: The default access specifier ispublic
class DefaultClass {
int x; // Private by default
};
struct DefaultStruct {
int y; // Public by default
};
int main() {
DefaultClass c;
DefaultStruct s;
// c.x = 10; // Error: private member
s.y = 20; // OK: public member
return 0;
}
Access Specifiers in Inheritance
In addition to controlling member access, access specifiers also play a role in inheritance. When a class is derived from another class, the mode of inheritance is specified using an access specifier:
class Base {
// Base class members
};
class Derived : [access-specifier] Base {
// Derived class members
};
The inheritance access specifier can be:
publicprotectedprivate
Public Inheritance
When a class inherits publicly from a base class, the access levels remain as follows:
- Public members of the base class remain public in the derived class
- Protected members of the base class remain protected in the derived class
- Private members of the base class remain inaccessible in the derived class
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class PublicDerived : public Base {
void accessTest() {
publicVar = 1; // OK: public member in base class
protectedVar = 2; // OK: protected member in base class
// privateVar = 3; // Error: private member in base class
}
};
int main() {
PublicDerived d;
d.publicVar = 10; // OK: public member
// d.protectedVar = 20; // Error: protected member
// d.privateVar = 30; // Error: private member
}
Protected Inheritance
When a class inherits protectedly from a base class, the access levels are modified as follows:
- Public members of the base class become protected in the derived class
- Protected members of the base class remain protected in the derived class
- Private members of the base class remain inaccessible in the derived class
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class ProtectedDerived : protected Base {
void accessTest() {
publicVar = 1; // OK: public in base, now protected
protectedVar = 2; // OK: protected member in base class
// privateVar = 3; // Error: private member in base class
}
};
// Another class derived from ProtectedDerived
class Further : public ProtectedDerived {
void accessTest() {
publicVar = 4; // OK: protected in ProtectedDerived
protectedVar = 5; // OK: protected in ProtectedDerived
// privateVar = 6; // Error: inaccessible
}
};
int main() {
ProtectedDerived d;
// d.publicVar = 10; // Error: base's public member is now protected
// d.protectedVar = 20; // Error: protected member
// d.privateVar = 30; // Error: private member
}
Private Inheritance
When a class inherits privately from a base class, the access levels are modified as follows:
- Public members of the base class become private in the derived class
- Protected members of the base class become private in the derived class
- Private members of the base class remain inaccessible in the derived class
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class PrivateDerived : private Base {
void accessTest() {
publicVar = 1; // OK: public in base, now private
protectedVar = 2; // OK: protected in base, now private
// privateVar = 3; // Error: private member in base class
}
};
// Another class derived from PrivateDerived
class Further : public PrivateDerived {
void accessTest() {
// publicVar = 4; // Error: now private in PrivateDerived
// protectedVar = 5; // Error: now private in PrivateDerived
// privateVar = 6; // Error: inaccessible
}
};
int main() {
PrivateDerived d;
// d.publicVar = 10; // Error: base's public member is now private
// d.protectedVar = 20; // Error: protected member is now private
// d.privateVar = 30; // Error: private member
}
Summary of Inheritance Access Rules
The following table summarizes how base class members are inherited in a derived class:
| Base Class Member | public Inheritance | protected Inheritance | private Inheritance |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | Not accessible | Not accessible | Not accessible |
Access Specifiers and Getter/Setter Methods
To maintain encapsulation while still allowing controlled access to private members, C++ classes often implement getter and setter methods:
#include <iostream>
#include <string>
using namespace std;
class Employee {
private:
string name;
int id;
double salary;
public:
// Constructor
Employee(string n, int i, double s) : name(n), id(i), salary(s) {}
// Getter methods
string getName() const {
return name;
}
int getId() const {
return id;
}
double getSalary() const {
return salary;
}
// Setter methods
void setName(string n) {
name = n;
}
void setId(int i) {
if (i > 0) { // Validation
id = i;
}
}
void setSalary(double s) {
if (s >= 0) { // Validation
salary = s;
}
}
// Method to give a raise
void giveRaise(double percentage) {
if (percentage > 0) {
salary += salary * (percentage / 100);
}
}
};
int main() {
Employee emp("John Doe", 12345, 50000);
// Using getter methods
cout << "Employee Details:" << endl;
cout << "Name: " << emp.getName() << endl;
cout << "ID: " << emp.getId() << endl;
cout << "Salary: $" << emp.getSalary() << endl;
// Using setter methods
emp.setName("Jane Doe");
emp.setSalary(55000);
// Invalid attempt - will be rejected by validation
emp.setId(-100);
// Give a raise
emp.giveRaise(10);
cout << "\nUpdated Employee Details:" << endl;
cout << "Name: " << emp.getName() << endl;
cout << "ID: " << emp.getId() << endl;
cout << "Salary: $" << emp.getSalary() << endl;
return 0;
}
Friend Class and Friend Function
Sometimes you need to allow a specific function or class to access private and protected members of a class. C++ provides the friend keyword for this purpose:
Friend Function
#include <iostream>
using namespace std;
class Box {
private:
double length;
double width;
double height;
public:
Box(double l, double w, double h) : length(l), width(w), height(h) {}
double getVolume() {
return length * width * height;
}
// Declaration of friend function
friend void displayBoxDimensions(const Box& box);
};
// Definition of friend function
void displayBoxDimensions(const Box& box) {
// Can access private members
cout << "Box dimensions: " << box.length << " x "
<< box.width << " x " << box.height << endl;
}
int main() {
Box box(10, 8, 6);
displayBoxDimensions(box);
cout << "Volume: " << box.getVolume() << endl;
return 0;
}
Friend Class
#include <iostream>
using namespace std;
class Box; // Forward declaration
class BoxManager {
public:
void setBoxDimensions(Box& box, double l, double w, double h);
void displayBoxInfo(const Box& box);
};
class Box {
private:
double length;
double width;
double height;
public:
Box() : length(0), width(0), height(0) {}
double getVolume() {
return length * width * height;
}
// Declare BoxManager as a friend class
friend class BoxManager;
};
// BoxManager member functions defined after Box class
void BoxManager::setBoxDimensions(Box& box, double l, double w, double h) {
box.length = l;
box.width = w;
box.height = h;
}
void BoxManager::displayBoxInfo(const Box& box) {
cout << "Box dimensions: " << box.length << " x "
<< box.width << " x " << box.height << endl;
cout << "Volume: " << box.length * box.width * box.height << endl;
}
int main() {
Box box;
BoxManager manager;
manager.setBoxDimensions(box, 10, 8, 6);
manager.displayBoxInfo(box);
return 0;
}
Best Practices for Using Access Specifiers
-
Default to private: Start with private members and only make them more accessible when necessary.
-
Use public for the interface: Methods that clients of the class will need should be public.
-
Use protected for inheritance: Members that derived classes will need access to should be protected.
-
Use getters and setters: Provide controlled access to private data through public methods.
-
Be cautious with friend declarations: Use friends sparingly as they break encapsulation.
-
Consider the ‘principle of least privilege’: Give each component only the access it needs.
-
Prefer public inheritance: It represents an “is-a” relationship and is most commonly used.
-
Document access decisions: Clearly comment why a member has a particular access level, especially for non-obvious cases.
Summary
Access specifiers are a core feature of C++ that support encapsulation by controlling how members of a class can be accessed. By using public, protected, and private access specifiers appropriately, you can create well-encapsulated classes that hide their implementation details while providing a clean interface for other code to use. Understanding how access specifiers work, both for class members and in inheritance, is essential for effective object-oriented programming in C++.