Open In App

Can We Inherit STL Containers?

Last Updated : 24 Jul, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

In C++, a common question that arises in our mind is: Can we inherit from Standard Template Library (STL) containers like std::vector, std::map, or std::list? The straightforward answer is no, we should not inherit from STL containers.

In this article, we will learn why inheriting from STL containers is not advisable and explore some practical alternatives for extending their functionality.

Problem with Inheriting STL Containers

1. STL Containers Are Not Designed for Inheritance

In C++ , STL containers are designed to be flexible and efficient for managing collections of elements. They are intended to be used through composition rather than inheritance. This means they should be components within other classes, not base classes to inherit from.

2. Lack of Protected Members and Virtual Destructors

Most STL containers do not have protected members, which means they do not expose internal implementation details that we might want to override or extend. Additionally, STL containers lack virtual destructors. Without a virtual destructor, even if we inherit from an STL container and create objects dynamically, deleting a derived class object through a base class pointer (e.g., std::vector*) will lead to undefined behavior.

3. Inheritance Can Lead to Unexpected Behavior

Inheriting from STL containers and overriding their member functions can result in unexpected and undefined behavior. The internal logic of STL containers assumes its own implementation and extending STL containers may cause compatibility issues with standard algorithms and other parts of the STL, which expect a standard-compliant container.

4. Breaking Encapsulation

Inheriting from an STL container can expose internal data structures and behaviors, thus losing the encapsulation provided by the container. This can even lead to maintenance problems because changes in the implementation of the STL container might break our derived class.

Alternatives to Extend the Functionality of STL Containers

The following are some of the alternatives that can be used to extend the functionality of STL containers in C++:

1. Composition

In composition what we do is, create a new class that contains an STL container as a member, which allows us to add new functionalities while maintaining encapsulation. This approach is often referred to as a “has-a” relationship.

Example:

The below example demonstrates the usage of composition, it includes an example of a library class that has a std::vector<Book> as a member because a library “has-a” collection of books.

C++
#include <iostream>
#include <string>
#include <vector>

using namespace std;

// Book class
class Book {
public:
    // Public member variables for book title and author
    string title;
    string author;

    // Constructor to initialize a Book object with a title
    // and author
    Book(const string& t, const string& a)
        : title(t)
        , author(a)
    {
    }
};

// Library class that uses composition with vector
class Library {
private:
    // Private member vector to hold books
    vector<Book> books;

public:
    // Method to add a book to the library
    void addBook(const Book& book)
    {
        books.push_back(book);
    }

    // Method to print all books in the library
    void printBooks() const
    {
        for (const auto& book : books) {
            cout << "Title: " << book.title
                 << ", Author: " << book.author << endl;
        }
    }
};

int main()
{
    // Create a Library object
    Library library;

    // Add books to the library
    library.addBook(Book("The C++ Programming Language",
                         "Bjarne Stroustrup"));
    library.addBook(
        Book("Effective Modern C++", "Scott Meyers"));

    // Print all books in the library
    library.printBooks();

    return 0;
}

Output
Title: The C++ Programming Language, Author: Bjarne Stroustrup
Title: Effective Modern C++, Author: Scott Meyers

2. Aggregation

Aggregation is similar to composition only but it has a key difference that the contained objects (STL containers) can exist independently of the parent class. This is useful when the lifecycle of the contained objects is not tied to the lifecycle of the parent class.

Example:

The below example demonstrates the usage of aggregation that includes an example of a Project class that have a std::vector<TeamMember> as a member, where TeamMember instances can exist independently of the Project.

C++
#include <iostream>
#include <string>
#include <vector>

using namespace std;

// TeamMember class
class TeamMember {
public:
    // Public member variable for team member name
    string name;

    // Constructor to initialize a TeamMember object with a
    // name
    TeamMember(const string& n)
        : name(n)
    {
    }
};

// Project class that uses aggregation with vector
class Project {
private:
    // Private member vector to hold team members
    vector<TeamMember*> teamMembers;

public:
    // Method to add a team member to the project
    void addTeamMember(TeamMember* member)
    {
        teamMembers.push_back(member);
    }

    // Method to print all team members in the project
    void printTeamMembers() const
    {
        for (const auto& member : teamMembers) {
            cout << "Team Member: " << member->name << endl;
        }
    }
};

int main()
{
    // Create team member objects
    TeamMember alice("Alice");
    TeamMember bob("Bob");

    // Create a Project object
    Project project;

    // Add team members to the project
    project.addTeamMember(&alice);
    project.addTeamMember(&bob);

    // Print all team members in the project
    project.printTeamMembers();

    return 0;
}

Output
Team Member: Alice
Team Member: Bob

3. Wrapper Classes

Wrapper class is another alternative in which we create a new class that encapsulates an STL container and provides additional functionalities. This is another way to achieve composition with more control over the interface exposed to the user.

Example:

The below example demonstrates the usage of wrapper class, it includes an example of a Queue class that wraps a std::deque to provide queue-specific operations.

C++
#include <deque>
#include <iostream>

using namespace std;

// Queue class that wraps deque
class Queue {
private:
    // Private member deque to hold elements
    deque<int> elements;

public:
    // Method to enqueue an element
    void enqueue(int element)
    {
        elements.push_back(element);
    }

    // Method to dequeue an element
    int dequeue()
    {
        if (elements.empty()) {
            throw out_of_range("Queue is empty");
        }
        int front = elements.front();
        elements.pop_front();
        return front;
    }

    // Method to check if the queue is empty
    bool isEmpty() const { return elements.empty(); }
};

int main()
{
    // Create a Queue object
    Queue queue;

    // Enqueue elements 1, 2, and 3 into the queue
    queue.enqueue(1);
    queue.enqueue(2);
    queue.enqueue(3);

    // Dequeue and print all elements in the queue
    while (!queue.isEmpty()) {
        cout << queue.dequeue() << " ";
    }
    cout << endl;

    return 0;
}

Output
1 2 3 

Conclusion

While it might seem very convenient to inherit from STL containers, but it introduces several risks and complications that we must avoid. Using composition, aggregation, or wrapper classes can help us extend the functionality of STL containers in a safer and more maintainable way. Therefore, the answer to whether we should inherit from STL containers is a definitive no.


Next Article
Article Tags :
Practice Tags :

Similar Reads