This is my general question: Is it safe to call a non-virtual base class member function from the base class destructor using a derived class pointer that is getting destroyed?
Let me explain this by the following example.
I have a Base class and a derived Key class.
static unsigned int count = 0;
class Base;
class Key;
void notify(const Base *b);
class Base
{
public:
Base(): id(count++) {}
virtual ~Base() { notify(this); }
int getId() const { return id; }
virtual int dummy() const = 0;
private:
unsigned int id;
};
class Key : public Base
{
public:
Key() : Base() {}
~Key() {}
int dummy() const override { return 0; }
};
I now create an std::map (std::set will also work) of derived Key class pointers sorted by their id as follows:
struct Comparator1
{
bool operator()(const Key *k1, const Key *k2) const
{
return k1->getId() < k2->getId();
}
};
std::map<const Key*, int, Comparator1> myMap;
Now as and when a Key gets deleted, I want to erase that key from myMap. To do this I first tried implementing the notify method triggered from ~Base() as follows, but I know this is not safe and can result in an undefined-behavior. I have verified this here: http://coliru.stacked-crooked.com/a/4e6cd86a9706afa1
void notify(const Base* b)
{
myMap.erase(static_cast<const Key *>(b)); //not safe, results in UB
}
So to circumvent this issue, I defined a heterogenous Comparator and used variant (4) of std::map::find to find the key in the map and then passed that iterator to erase as follows:
struct Comparator2
{
using is_transparent = std::true_type;
bool operator()(const Key *k1, const Key *k2) const
{
return k1->getId() < k2->getId();
}
bool operator()(const Key *k1, const Base *b1) const
{
return k1->getId() < b1->getId();
}
bool operator()(const Base *b1, const Key *k1) const
{
return b1->getId() < k1->getId();
}
};
std::map<const Key*, int, Comparator2> myMap;
void notify(const Base* b)
{
// myMap.erase(static_cast<const Key *>(b)); //not safe, results in UB
auto it = myMap.find(b);
if (it != myMap.end())
myMap.erase(it);
}
I have tested this second version with g++ and clang and I am not seeing any undefined behavior. You can try the code here: http://coliru.stacked-crooked.com/a/65f6e7498bdf06f7
So is my second version using Comparator2 and std::map::find safe? As inside the Comparator2, I am still using a pointer to the derived Key class whose destructor has already been called. I do not see any error using g++ or clang compiler, so could you please advise if this code is safe?
Thanks,
Varun
Edit: I just realized that Comparator2 can be further simplified by directly using the Base class pointer as follows:
struct Comparator2
{
using is_transparent = std::true_type;
bool operator()(const Base *k1, const Base *k2) const
{
return k1->getId() < k2->getId();
}
};
This also works: http://coliru.stacked-crooked.com/a/c7c10c115c20f5b6