【问题标题】:Best way to protect from multiple shared_ptr to the same object保护多个 shared_ptr 到同一个对象的最佳方法
【发布时间】:2012-06-27 10:14:36
【问题描述】:

将相同的指针发送到两个不同的shared_ptr 是不好的,它会导致双重释放,如下所示:

int* p = new int;
std::shared_ptr<int> p1(p);
std::shared_ptr<int> p2(p); // BAD

你可以用std::enable_shared_from_this实现同样的目的:

class Good: public std::enable_shared_from_this<Good>
{
public:
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

int main()
{
    std::shared_ptr<Good> gp1(new Good);
    std::shared_ptr<Good> gp2 = gp1->getptr();
}

但这仍然不能防止:

class Good: public std::enable_shared_from_this<Good>
{
public:
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

int main()
{
    Good* p = new Good;
    std::shared_ptr<Good> gp3(p);
    std::shared_ptr<Good> gp4(p); // BAD
}

如果你有这样的代码,这可能会成为一个问题:

void Function(std::shared_ptr<Good> p)
{
    std::cout << p.use_count() << '\n';
}

int main()
{
    Good* p = new Good;
    std::shared_ptr<Good> p1(p);
    Function(p);    // BAD
}

当有智能指针时,为什么我要使用常规指针?因为在性能关键代码中(或为方便起见)shared_ptr 或weak_ptr 的开销是不可取的。

为了防止这个错误,我做了:

class CResource : public shared_ptr<Good>
{
public:
    CResource()
    {
    }

    CResource(std::shared_ptr<CBaseControl> c)
        : CResource(c)
    {
    }

private:
    CResource(CBaseControl* p)
    {
    }
};

void Function(CResource p)
{
    std::cout << p.use_count() << '\n';
}

int main()
{
    Good* p = new Good;
    CResource p1(std::shared_ptr<Good>(p));
    Function(p);    // Error
}

如果有人试图用指针而不是shared_ptr 调用Function,这将导致编译器出错。但这并不能阻止某人声明void Function(std::shared_ptr p),但我认为这不太可能。

这仍然很糟糕吗?有更好的方法吗?

【问题讨论】:

  • shared_ptrweak_ptr 的开销是不可取的” 你真的可以量化这个开销吗?我参与了一个项目,该项目执行一些具有实时要求(例如帧速率)的大量图像处理工作,我们经常使用shared_ptr,因为 real 开销在其他地方算法。
  • 但是你真的可以量化这个开销吗?您是否运行了基准测试并证明是的,std 托管指针的代价太高了?我认为您正在进行过早的优化。还是只是您不喜欢在使用时输入额外的调用来锁定每个weak_ptr
  • 让我感到惊讶。我倾向于责怪您的系统架构,但从这里看不到。
  • 您的 BAD 示例不应编译,shared_ptr 有一个 explicit 构造函数,因此您无法将原始指针传递给采用 shared_ptr 的函数。正如康拉德的回答所说,只是不要以明显有风险的风格来写它。问题基本上是“如果我写好代码是安全的,如果我写坏代码就不是”。所以不要那样做。
  • @JorgeRodriguez:如果你使用boost::intrusive_ptr,你可以获得原始指针的性能,因为它没有额外的间接级别。

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


【解决方案1】:

解决方案很简单:从一开始就永远不要拥有原始指针自己的内存。这种模式:

int* p = new int;
std::shared_ptr<int> p1(p);
std::shared_ptr<int> p2(p); // BAD

根本不应该存在。从您的代码库中消除它。在 C++11 中,new 唯一合法的地方是作为参数 对智能指针的构造函数调用(或在 非常 低级别的东西中)。

即有这样的代码:

std::shared_ptr<int> p1(new int);

或者更好(不再涉及裸体new):

auto p1 = std::make_shared<int>();

请注意,在代码中使用原始指针很好(但在我见过的大多数 C++ 代码中,我什至会质疑)。但是如果你使用原始指针,不要让它们拥有内存。要么指向自动存储(无需资源管理),要么使用unique_ptr 并通过其get 成员函数访问原始指针。

【讨论】:

  • 好点。明确您的所有权语义就足以让您的问题不出现。此外,请记住,当不需要共享所有权时,您可以使用 unique_ptr 而不会产生任何开销。
  • @Nawaz 你可以提供一个自定义删除器作为std::shared_ptr 的模板参数,它会调用delete[] 而不是delete。与unique_ptr 不同(您可以说unique_ptr&lt;int[]&gt;,由于某种原因,默认情况下shared_ptr 没有一个stackoverflow.com/questions/8947579/…
  • @Vino 就其本身而言,这不会造成问题。您可以拥有一个使用原始指针的函数。只是不要在任何地方删除它,或者用它来构造另一个智能指针。
  • @Vino,使用原始指针 point 指向一个对象是可以的。使用指向拥有对象的原始指针不太好,使用shared_ptrunique_ptr或类似类型拥有对象。如果您需要将所有权转移给另一个函数,请将智能指针传递给该函数。
  • @Vino:您可以通过始终传递智能指针来防止错误的方式;-) 替代方案在您的文档中明确说明代码的使用者不要自己进行内存管理。一旦您将其他可用的工具装箱,这与 C++ 一样好。
【解决方案2】:

这个例子甚至没有编译:

void Function(std::shared_ptr<Good> p)
{
    std::cout << p.use_count() << '\n';
}

int main()
{
    Good* p = new Good;
    std::shared_ptr<Good> p1(p);
    Function(p);    // BAD
}

shared_ptr 有一个 explicit 构造函数恰好 可以阻止这种情况发生。

要进行编译,您需要编写:

    Function( std::shared_ptr<Good>(p) );

这显然是错误的,如果有人要犯这个错误,他们也很可能会这样做:

    Function( CResource(std::shared_ptr<Good>(p)) );

那你为什么要写CResource?它增加了什么?

扩展 Konrad Rudolph 的出色回答:

如何避免问题的答案是遵循RAII 成语,但完全遵循它。

我们将忽略甚至无法编译的示例并查看上面的示例:

Good* p = new Good;
std::shared_ptr<Good> gp3(p);
std::shared_ptr<Good> gp4(p); // BAD

该代码未能遵循 RAII 习语。您获得资源:

    Good* p = new Good;

但不要初始化 RAII 类型。 糟糕

然后你用一些现有资源初始化一个对象:

    std::shared_ptr<Good> gp3(p);

这也是不好。您应该在获取资源的同时初始化一个 RAII 类型,而不是单独(甚至不能只用一行)。

然后你重复同样的错误:

    std::shared_ptr<Good> gp4(p); // BAD

您已将此行标记为“BAD”,但实际上前两行 只是 很糟糕。第三行将导致未定义的行为,但前两行允许该错误潜入,当它应该变得更加困难时。如果您从来没有使用过Good*,那么您就不能使用它来初始化gp4,您需要说shared_ptr&lt;Good&gt; gp4(gp3.get()),这太显然错了,没有人会这样做.

规则很简单:不要将原始指针放入shared_ptr。您没有分配的原始指针不是资源获取,所以不要将它用于初始化。在Function 内部也是如此,它不应该使用原始指针并使用它来初始化一个拥有该类型所有权的shared_ptr

这是 C++,因此无法防止所有错误的代码编写方式,您无法防止有足够动力的白痴自欺欺人,但所有的指导方针和工具都可以防止这种情况发生,如果您遵循指南。

如果您有一个接口强制您通过传递原始指针来转移所有权,请尝试替换该接口以便它使用unique_ptr 进行所有权转移,或者如果完全不可能这样做然后尝试使用unique_ptr 将接口包装在更安全的版本中以进行所有权转移,并且仅作为最后的手段,使用危险的接口,但非常明确地记录所有权已转移。

【讨论】:

    猜你喜欢
    • 2021-02-11
    • 1970-01-01
    • 2019-07-19
    • 1970-01-01
    • 2016-05-09
    • 2018-02-25
    • 1970-01-01
    • 2016-03-07
    • 1970-01-01
    相关资源
    最近更新 更多