【问题标题】:Using members of base class after destruction of derived class在派生类销毁后使用基类的成员
【发布时间】:2017-05-10 04:51:29
【问题描述】:

假设我们有一个简单的结构:

struct RefCounters {
    size_t strong_cnt;
    size_t weak_cnt;
    RefCounters() : strong_cnt(0), weak_cnt(0) {}
};

从实现的角度来看,析构函数RefCounters::~RefCounters 应该什么都不做,因为它的所有成员都有原始类型。这意味着如果这种类型的对象通过显式调用析构函数被销毁(但它的内存没有被释放),那么我们将能够在对象死亡后正常使用它的成员。

现在假设我们还有一些从RefCounters 派生的类。假设RefCountersDerived 类的基类中只出现一次。假设为 Derived 类的对象显式调用析构函数,但它的内存没有被释放。之后可以访问成员strong_cntweak_cnt吗?

从实现的角度来看,应该没问题,至少在不涉及虚拟继承的情况下。因为Derived*可以静态转换为RefCounters*(添加编译时常量偏移到地址),RefCounters的内存不应该被Derived类的析构函数触及。

这是一个代码示例:

struct RefCounted : public RefCounters {
    virtual ~RefCounted() {}
};

struct Base : public RefCounted {
    int val1;
    virtual void print();
};

struct Derived : public Base {
    std::string val2;
    virtual void print();
};

Derived *pDer = new Derived();
pDer->~Derived();          //destroy object
pDer->strong_cnt++;        //modify its member
std::cout << pDer->strong_cnt << pDer->weak_cnt << "\n";

C++ 标准是否将此类代码视为未定义行为?是否有任何实际原因导致它无法正常工作?可以通过微小的更改或添加一些限制使其合法吗?

附:假设,这样的代码示例允许使 intrusive_ptr + weak_ptr 组合,这样如果至少有一个weak_ptr 仍然指向它,那么总是可以从对象指针中获取weak_ptr。更多详情请见this question

【问题讨论】:

  • 由于RefCounters 有一个微不足道的析构函数,根据[basic.life]/1,它的生命周期在其存储被重用或释放时结束。显式的析构函数调用是无操作的,不应该影响任何东西; RefCounters 的特定实例也不应该是更大对象的子对象。
  • 所以,RefCounters 对象在pDer-&gt;~Derived(); 之后应该仍然存在——但我相信通过pDer 访问它会表现出未定义的行为。像这样的东西应该可以工作:RefCounters* pRef = pDer; pDer-&gt;~Derived(); pRef-&gt;strong_cnt++;
  • @stgatilov static_cast&lt;RefCounters*&gt;(pDer)[basic.life]/(5.4) 明确禁止:“在对象的生命周期开始之前但在对象存储之后将占用的存储空间已经被分配,或者在对象的生命周期结束之后并且在对象占用的存储空间被重用或释放之前,任何指向该对象将要或曾经位于的存储位置的指针都可以使用,但只能在有限的方法...如果:...指针用作static_cast...的操作数,则程序具有未定义的行为。”
  • @stgatilov 但是,我认为RefCounters* pRef = pDer; 应该是有效的。 [basic.life]/(5.3) 说:“......如果程序具有未定义的行为:......指针被隐式转换(4.10)为指向虚拟基类的指针...... .” 这似乎表明隐式转换为指向非虚拟基类的指针是可以的。事实上,virtual 这个词是由DR597 的决议添加到这个子句中的,据我所知,为了允许这种隐式转换。

标签: c++ language-lawyer destructor


【解决方案1】:

我认为你的方法很糟糕。 cmets 中有一个很好的链接,显示了关于标准细节的辩论。一旦发生争论,不同的编译器很有可能会以不同的方式实现这个细节。更。同一个编译器可能会将其实现从一个版本更改为另一个版本。

你越是使用各种暗角,你遇到问题的机会就越大。

底线。愿意实现什么?为什么不能使用普通的 C++ 语言特性来做到这一点?

【讨论】:

  • 只需阅读“P.S.”问题的一部分,我在那里添加了一个链接。但是请不要发布诸如“你不应该那样做”之类的内容,因为这不是一个好的答案。
  • @stgatilov,看起来你需要修改你的问题,因为问题“是基类(及其 primitive 数据成员)在销毁派生类后仍然存在”很漂亮与讨论设计智能指针的方法无关。
  • 你问“这段代码合法吗?”,“能不能修改成合法的”?我的观点是,由于即使在委员会中也对此进行了辩论,所以怀疑应该转向“非法”的方向。至少在辩论得到解决并且编译器将与此解决方案保持一致之前,该代码是非法的。
  • 你是绝对正确的,智能指针的讨论在这个问题上完全是题外话。如果您对此感兴趣,只需转到链接的问题,它就是关于它的。所以没有必要朝那个方向重新研究这个问题。
  • 重做它以提示人们删除他们的反对票是有意义的。
猜你喜欢
  • 2016-05-21
  • 2023-03-24
  • 2017-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
相关资源
最近更新 更多