我使用boost::intrusive_ptr
了很多来保持某些类的实例处于活动状态。在某些时候,我的程序希望所有boost::intrusive_ptr
s 都已被删除,以便释放底层对象。
我最近似乎经常遇到的一个问题是,并非所有对象都boost::intrusive_ptr
被删除并且所述对象仍然“活着”。这是一个错误。
我需要能够检测到这些指针在哪里。获取创建它们的源文件/行号列表就足够了(例如,如果调试输出告诉我在此处创建的指针仍然存在:
foo.m_ptr = foo::create<Foo>();
其中 m_ptr 是 aboost::intrusive_ptr<Foo>
和/或boost::intrusive<Foo const>
然后我可以弄清楚仍然没有被破坏的指针是Foo::m_ptr
;)。
当然,在这种情况下,m_ptr
之前已经创建了 - 甚至可能已分配。所以这里重要的不是跟踪的构造函数,boost::intrusive_ptr
而是引用计数(最后)增加的地方。
上面的赋值函数如下所示:
intrusive_ptr<T>& operator=(intrusive_ptr<T> const& rhs)
{
intrusive_ptr<T>(rhs).swap(*this);
return *this;
}
因此,这个复制从 rhs 构造一个临时对象,增加 rhs 指向的对象的引用计数,然后将其交换,*this
以便临时对象现在指向*this
之前指向的对象,当前对象指向 rhs 指向的对象。最后,临时对象被破坏,导致*this
指向的引用计数减少。
因此,从 中获取调用地址intrusive_ptr_add_ref
需要展开堆栈两次(如果不是三次),可能取决于优化级别。
因此,我认为无法避免boost::intrusive_ptr
用我自己的类替换,比如说utils::intrusive_ptr
,它将实现上述功能,如下所示:
[[gnu::noinline]] intrusive_ptr<T>& operator=(intrusive_ptr<T> const& rhs)
{
intrusive_ptr<T>(rhs, __builtin_return_address(0)).swap(*this);
return *this;
}
它用于[[gnu::noinline]]
确保__builtin_return_address(0)
返回当前函数的返回地址。这个返回地址被传递给构造函数来注册它(返回地址可以很容易地转换为稍后的调用位置,即在打印仍然存在的指针列表时)。
同时需要删除存储的位置rhs
。在我看来,确保这一点的最佳方法是将位置逐字存储在其intrusive_ptr
本身中。然后,该位置也将被交换swap
并因此在离开此范围时被破坏。
现有intrusive_ptr
s 的注册可以通过它们的地址来完成:每个构造函数都有一个地址(其this
指针),该地址将是唯一的,并且可以用作映射中的键。销毁后,此密钥将被再次删除。
这样,使用地图,就有一个所有现有intrusive_ptr
对象的列表,这些对象存储自己的位置。
我的推理在这里正确吗?还是有其他方法可以做到这一点?
编辑:
我添加了一个新类,它基本上是 boost::intrusive_ptr 的副本,但去掉了任何 boost 并且只支持 c++17(及更高版本)。我还没有编译它,但这应该作为基础(?)。见https://github.com/CarloWood/ai-utils/blob/master/intrusive_ptr.h