多个shared_ptr存储相同的指针

时间:2012-04-26 17:43:36

标签: c++ c++11 shared-ptr

考虑这个程序:

#include <memory>
#include <iostream>

class X
  : public std::enable_shared_from_this<X>
{
public:
  struct Cleanup1 { void operator()(X*) const; };
  struct Cleanup2 { void operator()(X*) const; };
  std::shared_ptr<X> lock1();
  std::shared_ptr<X> lock2();
};

std::shared_ptr<X> X::lock1()
{
  std::cout << "Resource 1 locked" << std::endl;
  return std::shared_ptr<X>(this, Cleanup1());
}

std::shared_ptr<X> X::lock2()
{
  std::cout << "Resource 2 locked" << std::endl;
  return std::shared_ptr<X>(this, Cleanup2());
}

void X::Cleanup1::operator()(X*) const
{
  std::cout << "Resource 1 unlocked" << std::endl;
}

void X::Cleanup2::operator()(X*) const
{
  std::cout << "Resource 2 unlocked" << std::endl;
}

int main()
{
  std::cout << std::boolalpha;

  X x;
  std::shared_ptr<X> p1 = x.lock1();
  {
    std::shared_ptr<X> p2 = x.lock2();
  }
}

我在C ++ 11标准第20.7.2节中没有看到任何暗示任何此类内容无效的内容。有两个shared_ptr对象存储相同的指针&x但不共享所有权,并且使用不会终止*get()生命周期的“删除者”,但没有任何禁止它,这有点不寻常。 (如果其中任何一个完全无意,就很难解释为什么有些shared_ptr成员函数接受std::nullptr_t值。)正如预期的那样,程序输出:

Resource 1 locked
Resource 2 locked
Resource 2 unlocked
Resource 1 unlocked

但现在如果我向main()添加一点:

int main()
{
  std::cout << std::boolalpha;

  X x;
  std::shared_ptr<X> p1 = x.lock1();
  bool test1( x.shared_from_this() );
  std::cout << "x.shared_from_this() not empty: " << test1 << std::endl;
  {
    std::shared_ptr<X> p2 = x.lock2();
  }
  try {
    bool test2( x.shared_from_this() );
    std::cout << "x.shared_from_this() not empty: " << test2 << std::endl;
  } catch (std::exception& e) {
    std::cout << "caught: " << e.what() << std::endl;
  }
}
然后事情就变得棘手了。使用g ++ 4.6.3,我得到输出:

Resource 1 locked
x.shared_from_this() not empty: true
Resource 2 locked
Resource 2 unlocked
caught: std::bad_weak_ptr
Resource 1 unlocked

为什么第二次调用shared_from_this()会失败?满足20.7.2.4p7的所有要求:

  

要求: enable_shared_from_this<T>应为T的可访问基类。 *this应为t类型的对象T的子对象。至少应有一个拥有的shared_ptr实例p &t

[TXtxpp1。]

但是g ++的enable_shared_from_this基本上遵循20.7.2.4p10中(非规范)“注意”的建议实现,使用类weak_ptr中的私有enable_shared_from_this成员。如果不在enable_shared_from_this中做一些相当复杂的事情,似乎不可能解决这类问题。

这是标准中的缺陷吗? (如果是这样,这里不需要注释解决方案“应该”是什么:添加一个需求,以便示例程序调用未定义的行为,将注释更改为不建议这样一个简单的实现就足够了,....)

3 个答案:

答案 0 :(得分:5)

是的,C ++ 11中存在缺陷。允许这样做:

  

让两个shared_ptr对象存储相同的指针&amp; x但不共享所有权,并使用不会终止* get()生命周期的“删除者”,这有点不寻常,但没有任何条件禁止它。

这应明确声明为未定义的行为,无论是什么“删除者”所做的。当然,以某种方式做事在技术上可能并非违法。

但是,您撒谎给使用该代码的人。收到shared_ptr的任何人的期望是他们现在拥有该对象的所有权。只要他们保留shared_ptr(或其副本),它指向的对象仍然存在。

您的代码不是这种情况。所以我会说它在语法上是正确的但在语义上是无效的。

shared_from_this的语言很好。这是shared_ptr需要改变的语言。它应该声明它是未定义的行为来创建两个独立的指针“拥有”相同的指针。

答案 1 :(得分:0)

这个问题和其他相关问题在C ++ 17中得到澄清。现在std::enable_shared_from_this<T>被指定为拥有一个std::weak_ptr<T> weak_this;成员。对于std::shared_ptr的非数组专精,该成员由std::shared_ptr构造函数std::make_sharedstd::allocate_shared分配,如[util.smartptr.shared.const] /中所述1:

  

shared_­from_­this启用p,对于p类型的指针Y*,意味着如果Y具有明确且可访问的基类,则{ enable_­shared_­from_­this的特化,然后remove_­cv_­t<Y>*应隐式转换为T*,构造函数评估该语句:

if (p != nullptr && p->weak_this.expired())
  p->weak_this = shared_ptr<remove_cv_t<Y>>(*this, const_cast<remove_cv_t<Y>*>(p));

因此,我的OP中第二个main的正确行为现在不会抛出任何异常,并且两个“非空”检查都将显示为true。由于在致电lock2()时,内部weak_ptr已经拥有,因此不是expired()lock2()保持weak_ptr不变,因此第二次调用{ {1}}返回与shared_from_this()共享所有权的shared_ptr

答案 2 :(得分:0)

  X x;
  std::shared_ptr<X> p1 = x.lock1();
  (...sniped...)
}

这样的代码打破了&#34;拥有&#34;的语义。 &#34;智能指针&#34;:

  • 可以复制
  • 只要保留一个副本,所拥有的对象就会被保留

这种不变性非常重要,我认为应该通过代码审查来拒绝这种做法。但是你有一种变体可以满足不变量:

  • 必须动态管理对象(因此,不是自动的)
  • 任何拥有对象系列都拥有动态管理对象的所有权
  • 一个家庭的每个成员都拥有&#34;删除&#34;那个家庭

所以在这里我们共享拥有对象,这些对象属于不同的&#34;家庭&#34;拥有物品,它们不等同于#34;因为他们有不同:

  • &#34;删除器&#34;对象
  • use_count()
  • 控制块
  • owner_before结果

但它们都可以防止同一物体的破坏;这是通过在每个&#34;删除&#34;中保留shared_ptr的副本来完成的。对象

std::shared_from_this的干净替代用于完全控制std::weak_ptr<T>成员的初始化。

#include <memory>
#include <iostream>
#include <cassert>

// essentially like std::shared_from_this
// unlike std::shared_from_this the initialization IS NOT implicit
// calling set_owner forces YOU to THINK about what you are doing!

template <typename T>
class my_shared_from_this
{
    std::weak_ptr<T> weak;
public:
    void set_owner(std::shared_ptr<T>);
    std::shared_ptr<T> shared_from_this() const;
};

// shall be called exactly once
template <typename T>
void my_shared_from_this<T>::set_owner(std::shared_ptr<T> shared)
{
    assert (weak.expired());
    weak = shared;
}

template <typename T>
std::shared_ptr<T> my_shared_from_this<T>::shared_from_this() const
{
    assert (!weak.expired());
    return weak.lock();
}

class X : public my_shared_from_this<X>
{
public:
  struct Cleanup1 { 
    std::shared_ptr<X> own;
    Cleanup1 (std::shared_ptr<X> own) : own(own) {}
    void operator()(X*) const; 
  };

  struct Cleanup2 { 
    std::shared_ptr<X> own;
    Cleanup2 (std::shared_ptr<X> own) : own(own) {}
    void operator()(X*) const; 
  };

  std::shared_ptr<X> lock1();
  std::shared_ptr<X> lock2();

  X();
  ~X();
};

// new shared owner family with shared ownership with the other ones
std::shared_ptr<X> X::lock1()
{
  std::cout << "Resource 1 locked" << std::endl;
  // do NOT call set_owner here!!!
  return std::shared_ptr<X>(this, Cleanup1(shared_from_this()));
}

std::shared_ptr<X> X::lock2()
{
  std::cout << "Resource 2 locked" << std::endl;
  return std::shared_ptr<X>(this, Cleanup2(shared_from_this()));
}

void X::Cleanup1::operator()(X*) const
{
  std::cout << "Resource 1 unlocked" << std::endl;
}

void X::Cleanup2::operator()(X*) const
{
  std::cout << "Resource 2 unlocked" << std::endl;
}

X::X()
{
  std::cout << "X()" << std::endl;
}

X::~X()
{
  std::cout << "~X()" << std::endl;
}

// exposes construction and destruction of global vars
struct GlobDest {
  int id;
  explicit GlobDest(int id);
  ~GlobDest();
};

GlobDest::GlobDest(int id) 
  : id(id) 
{
    std::cout << "construction of glob_dest #" << id << std::endl;
}

GlobDest::~GlobDest() {
    std::cout << "destruction of glob_dest #" << id << std::endl;
}

GlobDest glob_dest0 {0};
std::shared_ptr<X> glob;
GlobDest glob_dest1 {1};

std::shared_ptr<X> make_shared_X()
{
    std::cout << "make_shared_X" << std::endl;
    std::shared_ptr<X> p = std::make_shared<X>();
    p->set_owner(p);
    return p;
}

int test()
{
  std::cout << std::boolalpha;

  std::shared_ptr<X> p = make_shared_X();
  static std::shared_ptr<X> stat;
  {
    std::shared_ptr<X> p1 = p->lock1();
    stat = p1;
    {
      std::shared_ptr<X> p2 = p->lock2();
      glob = p2;
      std::cout << "exit scope of p2" << std::endl;
    }
    std::cout << "exit scope of p1" << std::endl;
  }
  std::cout << "exit scope of p" << std::endl;
}

int main()
{
  test();
  std::cout << "exit main" << std::endl;
}

输出:

construction of glob_dest #0
construction of glob_dest #1
make_shared_X
X()
Resource 1 locked
Resource 2 locked
exit scope of p2
exit scope of p1
exit scope of p
exit main
Resource 1 unlocked
destruction of glob_dest #1
Resource 2 unlocked
~X()
destruction of glob_dest #0