std::unique_ptr,皮条和对象生存期

std::unique_ptr, pimpl and object lifetime

本文关键字:生存期 对象 unique ptr std      更新时间:2024-09-27

以下示例在Linux(GNU STL(上使用gcc 11编译,在FreeBSD(clang STL(上同时使用clang 12编译。在Linux上,它运行并打印值1和2。在FreeBSD上,它打印值1,然后用SEGV崩溃。我不太了解对象的生存期,所以整个过程可能是UB,运行时行为可能不相关。我知道std::unique_ptr在这两个STL之间的实现有一个重要的不同:Clang STL在析构函数开始时将std::unique_ptr的内部指针重置为nullptr,而GNU STL则不使用指针。

#include <iostream>
#include <memory>
struct C {
struct Private {
C* m_owner;
int m_x;
Private(C* owner) : m_owner(owner), m_x(0) {}
~Private() { m_owner->cleanup(); }
void cleanup() { std::cout << "Private x=" << ++m_x << 'n'; }
};

std::unique_ptr<Private> d;
C() { d = std::make_unique<Private>(this); }
~C() = default;
void cleanup() { d->cleanup(); }
};
int main(int argc, char **argv)
{
C c;
c.cleanup(); // For display purposes, print 1
return 0; // Destructors called, print 2
}

FreeBSD:上的输出

Private x=1
Segmentation fault (core dumped)

和一段回溯:

* thread #1, name = 'a.out', stop reason = signal SIGSEGV: invalid address (fault address: 0x8)
frame #0: 0x00000000002032b4 a.out`C::Private::cleanup() + 52
a.out`C::Private::cleanup:
->  0x2032b4 <+52>: movl   0x8(%rax), %esi

我认为这可能是UB的原因是:

  • return 0c的生命即将结束
  • 析构函数CCD_ 6运行。一旦析构函数的主体(默认(完成,对象的生存期就结束了,使用该对象就是UB
  • 现在运行对象的子对象(成员对象?(的析构函数
  • 则析构函数CCD_ 7运行。它为持有的对象运行析构函数
  • 析构函数CCD_ 8使用指向不再活动的对象CCD_

如果能给出一个答案,指出对对象寿命的理解是否正确,我将不胜感激。

如果它不是UB,那么就有一个单独的实现质量问题(或者我应该在调用d指针上的方法之前检查它,但这对一个pimpl来说似乎有点麻烦;然后我们得到一个STL实现所需的if(d)d->cleanup(),而在另一个STL实现中这是一个无用的检查(。

为了提出一个问题:在对象c的销毁过程中,此代码是否在语句m_owner->cleanup()(第9行(中显示UB?

是的,m_owner引用的对象的生存期已经结束,当调用m_owner->cleanup();时,它的析构函数调用完成。因此,呼叫为UB。