繁体   English   中英

使用派生类指针从基类析构函数调用非虚拟基类成员函数是否安全?

[英]Is it safe to call a non-virtual base class member function from the base class destructor using a derived class pointer?

这是我的一般问题:使用正在被销毁的派生类指针从基类析构函数调用非虚拟基类成员函数是否安全

让我通过下面的例子来解释这一点。

我有一个类和一个派生的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; }                                                                                                                    
};

我现在创建一个std::mapstd::set也可以工作)的派生Key类指针,按其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()触发的通知方法,如下所示,但我知道这不安全并且可能导致未定义的行为。 我在这里验证了这一点: http : //coliru.stacked-crooked.com/a/4e6cd86a9706afa1

void notify(const Base* b)
{
    myMap.erase(static_cast<const Key *>(b)); //not safe, results in UB
} 

因此,为了规避这个问题,我定义了一个异构 Comparator 并使用std::map::find 的变体 (4) 来查找映射中的键,然后通过该迭代器进行擦除,如下所示:

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

那么我使用Comparator2std::map::find 的第二个版本安全吗? Comparator2内部,我仍然使用指向已调用其析构函数的派生Key类的指针。 我在使用 g++ 或 clang 编译器时没有看到任何错误,所以你能告诉我这段代码是否安全吗?

谢谢,

瓦伦


编辑:我刚刚意识到Comparator2可以通过直接使用Base类指针来进一步简化,如下所示:

struct Comparator2                                                                                                                                          
{                                                                                                                                                             
  using is_transparent = std::true_type;                                                                                                                      
                                                                                                                                                              
  bool operator()(const Base *k1, const Base *k2) const                                                                                                  
  {                                                                                                                                                           
    return k1->getId() < k2->getId();                                                                                                                         
  }                                                                                                                                                                                                                                                                                      
};

这也有效: http : //coliru.stacked-crooked.com/a/c7c10c115c20f5b6

除非我误解了您的代码,否则这与具有破坏自身功能的对象基本相同(例如delete this;) - 这是合法的 - 前提是您在删除后不做任何依赖于您的对象存在的操作- 如调用成员函数或访问成员变量等...

所以看看你的代码,我认为你没问题——如果你再次使用它,你指向对象的指针现在是 UB,并且返回函数调用堆栈看起来很安全。

但我强烈建议另一种方法 - 这很可能是维护噩梦 - 如果一个毫无戒心的开发人员稍后更改此代码,他们很可能会导致 UB。

UnholySheep 为您管理所有这些的独立类的想法听起来好多了:)

更新

您在这里真正要做的就是调用一个普通函数( notify() ),该函数又通过 map.erase/find 通过比较器函数调用成员(非虚拟) getId()函数。 这一切都发生在析构函数范围内 - 这很好。 这是调用 delete 时发生的情况的粗略调用跟踪:

~Base()
    |
    v
  notify()
      |
      v
    Comparator() // This happens a number of times
        |
        v
      getId()    // This is called by Comparator
        |
   +----+           
   |
   v
~Base()          // base destructor returns

因此,您可以看到所有成员 ( getId() ) 调用都在 Base class d'tor 函数中完成 - 这是安全的。

我可能建议您不必编写“异构比较器”(Comparitor2),并使您的设计/工作更容易,就是让您的地图使用基类指针: std::map<const Base*, int, Comparator1> myMap; 然后你可以摆脱你的map.erase(b)结构,你可以直接在你的notify()函数中使用map.erase(b) ,所有这些都变得更清晰/更清晰。 这是一个带有一些注释(打印)的示例: https : //godbolt.org/z/h5zTc9

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM