Inheriting from STL containers

There’s actually a lot of argument, and frequently a lot of doubt as to whether you can create a class that inherits from a C++ STL container.

You absolutely can!

If you weren’t allowed to do it, the containers would be marked final, and they most certainly are not.

That said, STL containers don’t have virtual destructors, which means that while you can inherit from them quite safely and effectively, you must absolutely not perform polymorphism on them (do not try to access or delete them from a pointer to the base class. Period).

For most of the cases you might want to inherit from an STL container, that’s not really an issue.

I have several cases in Argus where I want to put some extra code into one or more of a container’s methods. I do it for certain kinds of error-checking or data-enforcement, where I don’t want to write variations of data-tests in dozens of places, or write dozens of exception handlers, or insert dozens of calls to data-validation functions.

Because Argus is data-driven, the original data is prone to have typos, duplicate data, references to things that don’t properly exist, and more. It is important to handle these cases as gracefully as possible, rather than taking the traditional route of aborting when something unexpected happens, or the slightly more modern version of just polluting your containers with junk and then behaving strangely.

Here’s how one of them looks:

class OidList final : public std::vector<Oid>
{
public:
 OidList() = default;
 OidList(const OidList&) = default;
 ~OidList() = default;
 inline void push_back(const Oid& val) {
 if (val == Oid::None)
 return;
 if (std::find(std::vector<Oid>::begin(), std::vector<Oid>::end(), val) != std::vector<Oid>::end())
 return; // Already in the list
 std::vector<Oid>::push_back(val);
 }
};

In this case, I wanted a vector of Oids (an internal Object type I use) to have certain properties. That is, I wanted all the elements to be unique (no duplicate Oids!), and I wanted one specific value of Oid (Oid::None) to never appear in the list.

There are other methods I might have modified, but my code only ever adds elements to an OidList via push_back() and never by any other method, so I’ve only got one place my checks are needed.

OidList itself started out as a simple typedef (typedef std::vector<Oid> OidList;), but with this redeclaration has been promoted to a first-class … well, class. And it is drop-in compatible – that is, I was able to simply remove the typedef, put this in, and the code compiles and runs without complaint or issue.

So, next time someone tells you that you can’t or shouldn’t inherit from an STL container, remember that you can, and that it is perfectly safe to do so, so long as you keep two things in mind:

  • No polymorphism. Never use pointers to the base class.
  • If you’re overriding methods, take care not to break the way basic container functionality works (begin/end/iterators, etc) or you’ll find that your standard algorithms will have some trouble trying to operate on your new class.