【问题标题】:shared_ptr deletes the objectshared_ptr 删除对象
【发布时间】:2011-09-09 20:43:10
【问题描述】:
void ClassName::LocalMethod( )
{
    boost::shared_ptr<ClassName> classNamePtr( this );

    //some operation with classNamePtr
    return;
}

这里的对象在从 LocalMethod() 返回时被释放,因为 classNamePtr 超出了范围。难道 shared_ptr 还不够聪明,无法知道 ClassName 对象仍在范围内而不是删除它吗?

【问题讨论】:

  • “让我知道解决这个问题的正确方法” - 做什么?你到底想做什么?一种方法可能是干脆不使用shared_ptr,而干脆使用this(或直接使用对象的成员,不使用this-&gt;)。但大概您出于某种原因尝试使用shared_ptr - 也许更直接地询问。
  • 问题:你为什么要创建一个 shared_ptr 放在首位?
  • 解决这个问题的正确方法很可能是重新设计。为什么你需要一个非动态分配的对象的 shared_ptr?

标签: c++ boost shared-ptr


【解决方案1】:

为对象创建shared_ptr 是什么意思?这意味着shared_ptr 的持有者现在拥有该对象的所有权。所有权意味着对象将在他需要时被删除。当shared_ptr 的持有者销毁其shared_ptr 时,这将导致该对象可能被销毁,假设该对象没有其他shared_ptrs。

shared_ptr 是类的成员时,这意味着shared_ptr 指向的对象的生命周期至少shared_ptr 指向的对象一样长是其中的一员。当shared_ptr 在堆栈上时,这意味着shared_ptr 指向的对象的生命周期将至少与创建它的范围一样长。一旦对象从堆栈中掉下来,它可能被删除。

唯一您应该将指针包装到shared_ptr 中的时间是分配对象最初。为什么?因为一个对象不知道它是否在shared_ptr 中。它不能知道。这意味着创建原始shared_ptr 的人现在有责任将其传递给需要共享该内存所有权的其他人。共享所有权起作用的唯一方法是通过shared_ptr复制构造函数。例如:

shared_ptr<int> p1 = new int(12);
shared_ptr<int> p2 = p1.get();
shared_ptr<int> p3 = p1;

shared_ptr 的复制构造函数在p1p3 之间创建共享所有权。请注意,p2 不与p1 共享所有权。他们都认为他们拥有相同内存的所有权,但这与共享内存不同。因为他们都认为他们拥有它的独特所有权。

因此,当三个指针被销毁时,会发生以下情况。首先,p3 将被销毁。但是由于 p3 和 p1 共享该整数的所有权,因此该整数还不会被销毁。接下来,p2 将被销毁。因为它认为它是整数的唯一持有者,所以它会销毁它。

此时,p1 指向已删除的内存。当p1被销毁时,它认为它是整数的唯一持有者,所以它会销毁它。这不好,因为它已经被销毁了。

你的问题是这样的。你在一个类的实例中。你需要调用你的一些函数来获取shared_ptr。但是您所拥有的只是this,它是一个常规指针。你是做什么的?

您将获得一些建议 enable_shared_from_this 的示例。但是考虑一个更相关的问题:“为什么这些函数将shared_ptr 作为参数?”

函数采用的指针类型表示该函数对其参数的作用。如果一个函数采用shared_ptr,这意味着它需要拥有指针。它需要共享内存的所有权。因此,请查看您的代码并询问这些函数是否真的需要 获得内存的所有权。他们是将shared_ptr 长期存储在某个地方(即:在一个对象中),还是只是在函数调用期间使用它们?

如果是后者,那么函数应该采用裸指针,而不是shared_ptr。这样,他们不能声明所有权。然后你的界面是自我记录的:指针类型解释了所有权。

但是,您可能正在调用真正需要共享所有权的函数。然后你需要使用enable_shared_from_this。首先,您的类需要从enable_shared_from_this 派生。然后,在函数中:

void ClassName::LocalMethod()
{
    boost::shared_ptr<ClassName> classNamePtr(shared_from_this());

    //some operation with classNamePtr
    return;
}

请注意,这里是有成本的。 enable_shared_from_this 在课堂上放了一个 boost::weak_ptr。但是没有虚拟开销之类的;它不会使类成为虚拟的。 enable_shared_from_this 是一个模板,所以你必须像这样声明它:

class ClassName : public boost::enable_shared_from_this<ClassName>

【讨论】:

  • 对于enable_shared_from_this,唯一要记住的是,您必须在调用shared_from_this() 之前的某个时间点将对象放在shared_ptr 中。我通常通过创建一个返回共享指针并使原始构造函数私有的静态工厂方法来强制执行此操作。
【解决方案2】:

shared_ptr 还不够聪明,无法知道 ClassName 对象是 还在范围内而不是删除它?

shared_ptr 不是这样工作的。当您在构造shared_ptr 时传递一个指针时,shared_ptr 将假定所有权 指针(在本例中为*this)。换句话说,shared_ptr 假设完全控制指针的生命周期,因为 shared_ptr 现在拥有它。因此,最后一个拥有指针对象的shared_ptr 将删除它。

如果在ClassName::LocalMethod() 之外没有classNamePtr 的副本,您可以传递一个在构造classNamePtr 时不执行任何操作的删除器。 Here's an example of a custom deleter being used to prevent a shared_ptr from deleting its pointee。根据您的情况调整示例:

struct null_deleter // Does nothing
{
    void operator()(void const*) const {}
};

void ClassName::LocalMethod() 
{
    // Construct a shared_ptr to this, but make it so that it doesn't
    // delete the pointee.
    boost::shared_ptr<ClassName> classNamePtr(this, null_deleter()); 
    // Some operation with classNamePtr 

    // The only shared_ptr here will go away as the stack unwinds,
    // but because of the null deleter it won't delete this.
    return; 
}

您也可以使用enable_shared_from_thisthis 获取shared_ptr。请注意,成员函数 shared_from_this() 仅在您现有的 shared_ptr 已经指向 this 时才有效。

class ClassName : public enable_shared_from_this<ClassName> 
{ 
public: 
    void LocalMethod()
    { 
        boost::shared_ptr<ClassName> classNamePtr = shared_from_this(); 
    } 
} 

// ...

// This must have been declared somewhere...
shared_ptr<ClassName> p(new ClassName);
// before you call this:
p->LocalMethod();

这是更合适的“官方”方法,它比 null 删除器方法更简单。

也可能是您实际上并不需要首先创建shared_ptr。评论//some operation with classNamePtr的部分有什么内容?可能有比前两种方法更好的方法。

【讨论】:

  • 我不相信空删除方法。如果他调用的那些函数期望声明对象的所有权,那么使用基于空删除器的函数将不允许他们这样做。他们稍后可能会指向死记忆。如果函数拥有所有权,那么为什么它们要以shared_ptr 开头呢?
  • @Nicol Bolas:这就是为什么我说“如果在ClassName::LocalMethod() 之外没有classNamePtr 的副本”。如果我知道注释“//使用 classNamePtr 进行某些操作”部分的内容,我可以给出更好的建议。话虽如此,我确实编辑了答案,指出enabled_shared_from_this 更合适,OP 应该重新考虑整体设计。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-01
  • 2012-11-17
  • 1970-01-01
  • 1970-01-01
  • 2021-11-13
相关资源
最近更新 更多