首页 > 解决方案 > C++ shared_ptr::operator* 危险吗?

问题描述

  shared_ptr<int> ptr = make_shared<int>(10);
  int& val = *ptr;
  ptr = make_shared<int>(20);
  // val now points to freed memory

在上面的代码中,我可以读取和写入指向已释放内存的 val。如果我们在 shared_ptr 中使用 .get(),同样的问题也适用。因此,即使我们求助于智能指针,也有可能在脚下开枪。

显然没有人会像上面那样编码。解决这个问题的一种方法是,如果我们有这样的东西 -

class Foo {
public:
   int& getVal() { return *p; }
private:
   shared_ptr<int> p;
};

有人可以调用 getVal() ,之后上述类的其他成员可以选择用不同的值覆盖 p。如果上面的 getVal() 返回一个 shared_ptr 而不是一个引用,我们将不会看到这个问题。有些人可能会争辩说,返回 shared_ptr 比返回引用更昂贵,因为我们需要增加 shared_ptr 控制块中的计数器。

那么,指南是否应该像上面那样不返回对 shared_ptr 的引用?

标签: c++c++11

解决方案


这不是operator*这里的危险,它是存储引用。

存储对任何事物的引用意味着您对所引用事物的生命周期负有个人责任。

int getVal() { return *p; }

是最安全、最简单的解决方案。C++ 崇尚价值观。

有一个复制成本高昂的更复杂的对象?然后多加注意。

gsl::span<int const> getVals() { return {p->data(), p->size()}; }

在这里,尽管不是引用或指针类型,但我们有一些概念上是引用的东西。

它的消费者必须知道getVals返回的生命周期规则。如果他们想持久化它,他们负责将数据复制出来。

只有在极端情况下,您才应考虑:

std::shared_ptr<gsl::span<int const>> getVals() {
  return p?make_shared_span<int const>( p, {p->data(), p->size()} ):nullptr;
}

where 在make_shared_span共享 ptr 中创建共享跨度视图。

共享对可变数据的引用是特别可怕的。你永远不知道数据是否会在你的脚下发生变化,这使得任何使用它的代码的状态与所有引用它的代码的状态纠缠在一起。


C++ 代码库解决“共享引用”和生命周期问题的一种方法是使用不可变数据。

实际上不可变的Ashared_ptr<gsl::span<int const>const>比使用.shared_ptr<gsl::span<int>>

您会在设计中看到这一点,例如此处或使用不可变的 libgit store支持文档。


通过将共享指针传递给周围的数据来“解决”这个问题将导致对象的寿命比它们应有的长得多,因为人们存储了一些指向它们的共享指针而不打算再次使用它。

关注寿命,你会得到美妙的RAII结果。

在某些情况下,共享共享指针是有意义的,那就是你真正想要共享所有权的时候,而不是“我不想考虑生命周期”的时候。共享所有权是一种更复杂的生命周期,使用它是因为在某些情况下,您不必考虑生命周期的结果,即生命周期的结果。


推荐阅读