I want to inherit from std::map
, but as far as I know std::map
hasn't any virtual destructor.
Is it therefore possible to call std::map
's destructor explicitly in my destructor to ensure proper object destruction?
The destructor does get called, even if it's not virtual, but that's not the issue.
You get undefined behavior if you attempt to delete an object of your type through a pointer to a std::map
.
Use composition instead of inheritance, std
containers are not meant to be inherited, and you shouldn't.
I'm assuming you want to extend the functionality of std::map
(say you want to find the minimum value), in which case you have two far better, and legal, options:
1) As suggested, you can use composition instead:
template<class K, class V>
class MyMap
{
std::map<K,V> m;
//wrapper methods
V getMin();
};
2) Free functions:
namespace MapFunctionality
{
template<class K, class V>
V getMin(const std::map<K,V> m);
}
There is a misconception: inheritance -outside the concept of pure OOP, that C++ isn't - is nothing more than a "composition with an unnamed member, with a decay capability".
The absence of virtual functions (and the destructor is not special, in this sense) makes your object not polymorphic, but if what you are doing is just "reuse it behavior and expose the native interface" inheritance does exactly what you asked.
Destructors don't need to be explicitly called from each other, since their call is always chained by specification.
#include <iostream>
unsing namespace std;
class A
{
public:
A() { cout << "A::A()" << endl; }
~A() { cout << "A::~A()" << endl; }
void hello() { cout << "A::hello()" << endl; }
};
class B: public A
{
public:
B() { cout << "B::B()" << endl; }
~B() { cout << "B::~B()" << endl; }
void hello() { cout << "B::hello()" << endl; }
};
int main()
{
B b;
b.hello();
return 0;
}
will output
A::A()
B::B()
B::hello()
B::~B()
A::~A()
Making A embedded into B with
class B
{
public:
A a;
B() { cout << "B::B()" << endl; }
~B() { cout << "B::~B()" << endl; }
void hello() { cout << "B::hello()" << endl; }
};
that will output exactly the same.
The "Don't derive if the destructor is not virtual" is not a C++ mandatory consequence, but just a commonly accepted not written (there's nothing in the spec about it: apart an UB calling delete on a base) rule that arises before C++99, when OOP by dynamic inheritance and virtual functions was the only programming paradigm C++ supported.
Of course, many programmers around the world made their bones with that kind of school (the same that teach iostreams as primitives, then moves to array and pointers, and on the very last lesson the teacher says "oh ... tehre is also the STL that has vector, string and other advanced features") and today, even if C++ becamed multiparadigm, still insist with this pure OOP rule.
In my sample A::~A() isn't virtual exactly as A::hello. What does it mean?
Simple: for the same reason calling A::hello
will not result in calling B::hello
, calling A::~A()
(by delete) will not result in B::~B()
. If you can accept -in you programming style- the first assertion, there are no reason you cannot accept the second. In my sample there is no A* p = new B
that will receive delete p
since A::~A isn't virtual and I know what it means.
Exactly that same reason that will not make, using the second example for B, A* p = &((new B)->a);
with a delete p;
, although this second case, perfectly dual with the first one, looks not interesting anyone for no apparent reasons.
The only problem is "maintenance", in the sense that -if yopur code is viewed by an OOP programmer- will refuse it, not because it is wrong in itself, but because he has been told to do so.
In fact, the "don't derive if the destructor is not virtual" is because the most of the programmers beleave that there are too many programmers that don't know they cannot call delete on a pointer to a base. (Sorry if this is not polite, but after 30+ year of programming experience I cannot see any other reason!)
But your question is different:
Calling B::~B() (by delete or by scope ending) will always result in A::~A() since A (whether it is embedded or inherited) is in any case part-of B.
Following Luchian comments: the Undefined behavior alluded above an in his comments is related to a deletion on a pointer-to-an-object's-base with no virtual destructor.
According to the OOP school, this results in the rule "don't derived if no virtual destructor exist".
What I'm pointing out, here, is that the reasons of that school depends on the fact that every OOP oriented object has to be polymorphic and everything is polymorphic must be addressable by pointer to a base, to allow object substitution. By making those assertion, that school is deliberately trying in making void the intersection between derived and non-replacable, so that a pure OOP program will not experience that UB.
My position, simply, admits that C++ is not just OOP, and not all the C++ objects HAVE TO BE OOP oriented by default, and, admitting OOP is not always a necessary need, also admits that C++ inheritance is not always necessarily servicing to OOP substitution.
std::map is NOT polymorphic so it's NOT replaceable. MyMap is the same: NOT polymorphic and NOT replaceable.
It simply has to reuse std::map and expose the same std::map interface. And inheritance is just the way to avoid a long boilerplate of rewritten functions that just calls the reused ones.
MyMap will not have virtual dtor as std::map does not have one. And this -to me- is enough to tell a C++ programmer that these are not polymorphic objects and that must not be used one in the place of the other.
I have to admit this position is not today shared by the most of the C++ experts. But I think (my only personal opinion) this is just only because of their history, that relate to OOP as a dogma to serve, not because of a C++ need. To me C++ is not a pure OOP language and must not necessarily always follow the OOP paradigm, in a context where OOP is not followed or required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With