我的一般问题是:使用正在被销毁的派生类指针从基类析构函数调用非虚基类成员函数是否安全?
让我通过以下示例进行解释。
我有一个Base类和一个派生Key类。
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; }
};
现在我创建了一个派生自Key类的指针的std::map(std::set也可以工作),并按它们的id进行排序,如下所示:
struct Comparator1
{
bool operator()(const Key *k1, const Key *k2) const
{
return k1->getId() < k2->getId();
}
};
std::map<const Key*, int, Comparator1> myMap;
当一个键被删除时,我希望从myMap中抹掉该键。为了做到这一点,我首先尝试实现从~Base()触发的notify方法,如下所示,但我知道这是不安全的,可能导致未定义的行为。 我已经在这里进行了验证:http://coliru.stacked-crooked.com/a/4e6cd86a9706afa1
void notify(const Base* b)
{
myMap.erase(static_cast<const Key *>(b)); //not safe, results in UB
}
因此,为了解决这个问题,我定义了一个异构比较器,并使用std::map::find的第四种重载形式,在地图中查找键,然后将该迭代器传递给erase,如下所示:
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);
}
我已使用g++和clang测试了第二个版本,并未发现任何未定义行为。您可以在此处尝试代码:http://coliru.stacked-crooked.com/a/65f6e7498bdf06f7 所以我的第二个版本是否安全,使用了Comparator2 和 std::map::find?因为在Comparator2中,我仍然使用指向已经调用析构函数的派生Key类的指针。我没有使用g++或clang编译器发现任何错误,所以请问这段代码是否安全?
谢谢, Varun
编辑:我刚意识到Comparator2可以进一步简化,直接使用Base类指针,如下所示:
struct Comparator2
{
using is_transparent = std::true_type;
bool operator()(const Base *k1, const Base *k2) const
{
return k1->getId() < k2->getId();
}
};
Key
的使用抽象化,让用户通过某个管理类(该类将是唯一允许创建和删除实例的类),也许结合某种智能指针来使用。 - UnholySheep