Road to C++ Programmer #5 - Access Speficiers

Last Edited: 9/13/2024

The blog post introduces how access specifiers work in C++.

C++ Access

Public & Private Access Specifiers

In a previous article, I briefly mentioned that the public keyword we use when defining attributes and methods is one of the access specifiers that allows them to be accessible from outside the class. Thus, with public access specifiers, we could do the following:

class Class {
    public:
        string public_attr;
        void public_print() {
            cout << "Public Method" << endl;
        };
};
 
int main () {
    Class public;
    public.public_attr = "Public";
    cout << public.public_attr << endl; // => Public
    public.public_print(); // => Public Method
 
    return 0;
}

However, there are cases where we do not want to allow access from outside the class. For example, we may not want user passwords or encryption algorithms to be publicly available. In such scenarios, we can use private access specifiers.

class BankAccount {
    public:
        string name;
        int set_password(string password) {
            string encrypted = this -> encrypt(password);
            this -> password = encrypted;
            return 1;
        };
    private:
        string password;
        string encrypt(string password) {
            // Encryption
        };
};
 
int main () {
    BankAccount user;
    user.name = "TK"; 
    user.password = "abcdefg"; // => Err
 
    user.set_password("abcdefg");
    user.encrypt("abcdefg"); // => Err
    return 0;
};

The private access specifier hides internal attributes and methods from the outside, allowing us to implement encapsulation and separation of concerns. For example, when building a machine learning model, we might use private access specifiers for the model's weights and biases, as they should only be modified through training.

If the user wants access to the model's weights and biases, it should be done through a public getter function that formats them appropriately. When initializing them, a setter function should be used to initialize weights automatically, rather than relying on manual user initialization.

Protected Access Specifiers

When the BankAccount class is inherited to create a child class like ABCBankAccount for ABC Bank, we cannot access the private attributes of BankAccount.

class ABCBankAccount : public BankAccount {
    public:
        float balance;
    ABCBankAccount(string name, string password) : name(name) {
        string encrypted = this -> encrypt(password); // => Err
        this -> password = encrypted; // => Err
        balance = 0;
    ;}
};

This is because the private access specifier prevents any outsider, including the child class, from accessing its members. To allow the child class to access them while still preventing access from outside the class, we can use the protected access specifier.

class BankAccount {
    public:
        string name;
        int set_password(string password) {
            string encrypted = this -> encrypt(password);
            this -> password = encrypted;
            return 1;
        };
    protected:
        string password;
        string encrypt(string password) {
            // Encryption
        };
};
 
class ABCBankAccount : public BankAccount {
    public:
        float balance;
    ABCBankAccount(string name, string password) : name(name) {
        string encrypted = this -> encrypt(password); // => OK
        this -> password = encrypted; // => OK
        balance = 0;
    ;}
};
 
int main () {
    ABCBankAccount user("TK", "abcdefg");
    cout << user.password << endl; // => Err
    user.encrypt("abcdefg"); // => Err
    return 0;
}

Base Class Access Specifiers

When using class inheritance, you might have seen the public keyword being used like class ABCBankAccount : public BankAccount. The public here is called a base class access specifier, and it dictates how the attributes and methods from the base class are inherited by the child class. Below is a table showing how member access levels change depending on the base class access specifiers:

Base Class Access Specifiers & Member Access Levels
Base Class Access SpecifierBase Class Public MemberBase Class Protected MemberBase Class Private Member
publicPublicProtectedInaccessible
protectedProtectedProtectedInaccessible
privatePrivatePrivateInaccessible

As we've seen, the private members of the base class are inaccessible to anyone, including the child class, regardless of the base class access specifier. However, the public and protected members' access depends on the base class access specifier.

When the base class access specifier is public, the public and protected members of the base class retain their access levels in the child class. When the base class access specifier is either protected or private, all public and protected members of the base class are downgraded to protected or private in the child class, respectively. (The default base class access specifier is private if it is not provided.)

Resources