An example of bad class design with STL containers: what’s the solution?

A class must not inherit from a STL container to replicate its behavior and add some few new functionalities. Let's see why...

The STL containers are largely used. Sometimes, they could be badly used. Let’s see an example for the std::queue.

The main problem

We want to implement a class that replicates or reuse the methods of a specific STL (Standard Template Library) container. In this post, I describe two possible approaches to solve this problem: in the former, a class inherits from a STL container (the dangerous class design); in the latter, a class “is made/composed of” the STL container (the correct class design).

A bad class design example: a class inheriting from a STL container

When thinking to this problem, it is easier to think of designing a class that directly inherits from the std::queue all of its functions. In the derived class, then all the additional methods can be implemented.

For easy of understanding, let’s assume we need to implement our own MyQueue class. This class is expected to provide the same functionalities of the std::queue<> with some additional methods (for the goal of this article, we can ignore these methods).

The class design here below is for lazy programmers and it is – for some reasons I’m going to explain in this post – a bad approach.

template<typename T>
class MyQueue : public std::queue<T>
{
public:
    MyQueue();
    virtual ~MyQueue();
    void SomeAdditionalMethod(int x);
};
template<typename T>
MyQueue<T>::MyQueue()
{
    std::cout << "Constructor called" << std::endl;
}
template<typename T>
MyQueue<T>::~MyQueue()
{
    std::cout << "Destructor called" << std::endl;
}
template<typename T>
void MyQueue<T>::SomeAdditionalMethod(int x)
{
    // ...
}

Using this class, we can perform all operations offered by the std::queue ( e.g., push(), emplace(), size(), … ). We can also dynamically allocate our new class MyQueue.

int main()
{
    std::queue<int>* ptr = new MyQueue<int>();
    ptr->push(5);
    std::cout << ptr->size() << std::endl;
    delete ptr; // DANGER!
    return 0;
}

What’s the big mistake in the proposed code? Let’s run the program and check the console output:

Constructor called
1
Press any key to continue . . .

As you can see, the class destructor has not been called and – in my case – the code has crashed when destroying the MyQueue object instance. Why?

The answer is that the STL containers are not designed to be inheritable. No virtual methods, all data members are private, no protected getters or setters functions… and – above all – no virtual class destructors

Actually, the destruction of the class instance leads to an undefined behaviour. Therefore, this class design is really dangerous!

The correct class design: when the class has a STL container

Instead, we should really be using the containers via composition rather than implementation inheritance, in a “has-a” approach rather than an “is-a” one.

Let’s redesign the same example shown above.

template<typename T>
class MyGoodQueue
{
public:
    MyGoodQueue();
    virtual ~MyGoodQueue();
    void SomeAdditionalMethod(const int x);
    void push(const T& x);
    size_t size() const;
private:
    std::queue<T> q;
};

template<typename T>
MyGoodQueue<T>::MyGoodQueue()
{
    std::cout << "Constructor called" << std::endl;
}
template<typename T>
MyGoodQueue<T>::~MyGoodQueue()
{
    std::cout << "Destructor called" << std::endl;
}
template<typename T>
void MyGoodQueue<T>::SomeAdditionalMethod(int x)
{
    // ...
}
template<typename T>
void MyGoodQueue<T>::push(const T& x)
{
    q.push(x);
}
template<typename T>
size_t MyGoodQueue<T>::size() const
{
    return q.size();
}

Without any change to the main function code, could you see the difference when using the class defined above?

Constructor called
1
Destructor called
Press any key to continue . . .

Conclusions

Inheriting from the STL containers is really a bad practice. Indeed, the class should “have” a container and not be designed to “be” a STL container. The main reason is that inheriting from the STL containers can lead to undefined behaviour because the STL containers are not designed to be inheritable.

Pietro L. Carotenuto
Pietro L. Carotenuto

C++ Software Engineer, Ph.D. in Information Engineering. I like reading and learning new things while working on new projects. Main author on PietroLC.com looking for contributions and guest authors.

Articles: 25

Leave your feedback here:

Your email address will not be published. Required fields are marked *