Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

C++ Inheritance Mechanics: Access Modifiers, Construction Order, and Name Hiding

Tech May 10 4

Inheritance Access Control and Rules

In C++, inheritance facilitates code reuse at the structural level. A derived class embeds the member variables of its base class rather than duplicating the logic. The syntax specifies the derived class, the inheritance mode, and the base class: class Derived : public Base.

Access Specifiers vs. Inheritance Modes

The accessibility of an inherited member is determined by the most restrictive qualifier between the base class member's access specifier and the inheritance mode. The hierarchy of accessibility from most to least permissive is public, protected, and private. For instance, if a public member is inherited using protected inheritance, it becomes protected in the derived class.

#include <iostream>

class BaseModule {
public:
    int publicId = 10;
protected:
    int internalState = 20;
private:
    int secretKey = 30;
};

class DerivedModule : public BaseModule {
public:
    void display() {
        std::cout << publicId << " " << internalState << std::endl;
    }
    int extraValue = 40;
};

int main() {
    DerivedModule dm;
    dm.display();
    return 0;
}

Although private members of the base class are inherited and occupy memory within the derived class object, they are strictly inaccessible to the derived class. A derived class cannot modify or read these members directly.

Friendship and Inheritance

Friendship is not inherited. If a function or class is declared as a friend of the base class, it does not gain access to the private or protected members of the derived class unless explicitly granted friendship there.

Differentiating Protected and Private

The protected specifier exists specifically for inheritance hierarchies. Both protected and private members are inaccessible from outside the class, but protected members remain visible to derived classes, whereas private members are completely hidden from all derived classes.

Default Inheritance Modes

If the inheritance mode is omitted, the default depends on the keyword used to define the derived class. The class keyword defaults to private inheritance, while the struct keyword defaults to public inheritance.

Constructor and Destructor Behavior in Inheritance

Initializing Base Class Members

When constructing a derived object, the base class portion must be initialized using the base class's constructor. You cannot directly initialize inherited base members in the derived class initializer list. The base subobject is treated as a single entity.

#include <iostream>

class NetworkNode {
public:
    NetworkNode(int ip, int port) : ipAddress(ip), portNumber(port) {}
    void printStatus() { std::cout << ipAddress << ":" << portNumber << std::endl; }
    int ipAddress;
protected:
    int portNumber;
};

class SecureNode : public NetworkNode {
public:
    SecureNode() : NetworkNode(192, 8080), encryptionLevel(5) {}
    void printStatus() { std::cout << "Encrypted:" << encryptionLevel << std::endl; }
    int encryptionLevel;
};

int main() {
    SecureNode node;
    node.NetworkNode::printStatus();
    node.printStatus();
    return 0;
}

Function Hiding (Redefinition)

When a derived class defines a function with the same name as a function in the base class, the base class function is hidden. This is not function overloading, because overloading requires both functions to exist within the same scope. Because the functions reside in different class scopes, the derived class's version hides all base class versions with the same name, regardless of parameter differences. To invoke the hidden base class function, the scope resolution operator must be used (e.g., Base::functionName()).

Construction and Destruction Order

Construction always occurs from the base class to the derived class (base first, derived second). Destruction follows the reverse order (derived first, base second). This order guarantees that base class members remain valid while the derived class is being constructed or destructed.

Internally, destructors are mangled to the same name (often destructor), which causes the base class destructor to be hidden by the derived class destructor. However, you must never explicitly call the base class destructor (e.g., Base::~Base()) within the derived class destructor. The compiler automatically invokes the base destructor after the derived destructor completes. Explicitly calling it results in double deletion and undefined behavior.

#include <iostream>

class ResourceHolder {
public:
    int* buffer = new int(100);
    virtual ~ResourceHolder() { delete buffer; std::cout << "Base destroyed" << std::endl; }
};

class ExtendedHolder : public ResourceHolder {
public:
    ExtendedHolder() : ptr(buffer) {}
    ~ExtendedHolder() {
        // Explicit call to base destructor is erroneous and causes use-after-free
        // ResourceHolder::~ResourceHolder(); 
        *ptr = 50; // Base must still be alive here
        std::cout << "Derived destroyed" << std::endl;
    }
    int* ptr;
};

int main() {
    ExtendedHolder ex;
    return 0;
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.