Access Specifiers in C++

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:

  1. public: Members can be accessed from anywhere
  2. protected: Members can be accessed within the class and by derived classes
  3. 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 is private
  • For struct: The default access specifier is public
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:

  • public
  • protected
  • private

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 Memberpublic Inheritanceprotected Inheritanceprivate Inheritance
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateNot accessibleNot accessibleNot 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

  1. Default to private: Start with private members and only make them more accessible when necessary.

  2. Use public for the interface: Methods that clients of the class will need should be public.

  3. Use protected for inheritance: Members that derived classes will need access to should be protected.

  4. Use getters and setters: Provide controlled access to private data through public methods.

  5. Be cautious with friend declarations: Use friends sparingly as they break encapsulation.

  6. Consider the ‘principle of least privilege’: Give each component only the access it needs.

  7. Prefer public inheritance: It represents an “is-a” relationship and is most commonly used.

  8. 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++.