【问题标题】:smart pointers - why use them, and which to use?智能指针 - 为什么使用它们,使用哪个?
【发布时间】:2011-11-23 12:38:38
【问题描述】:

一般问题
现在我已经阅读了很多关于智能指针的文章,在许多情况下,共享指针看起来像是“完美的”。但是我也读到了周期性参考或类似的东西? shared_ptr哪里不能用?我很难理解这一点,有人可以举一个简单的例子来说明这一点吗?

另外我真的很想知道,weak_ptr 提供了哪些普通指针不提供的功能? - 因为他们不增加引用计数,所以他们不能保证他们指向的内存仍然有效?

我的个人项目:
在一个项目中,我有 2 个“全局”容器(两个容器都将很快在一个类中移动),它们都充满了“对象”。然而,两者都应该“指向”同一个对象。一个对象不能存在于这些容器之外,也不应该有一个容器包含它,而另一个容器不包含它。

目前我只是为此使用普通指针,并有一个createObject& destroyObject 方法来管理内存。

这是好的设计吗?我应该使用智能指针吗?

【问题讨论】:

  • 先看看这个线程:smart pointers (boost) explained
  • weak_ptr 保证它将指向一个有效对象或将是null。相比之下,原始指针无法判断指向的对象是否已被销毁。
  • 由于存在这么多类型的智能指针,我讨厌它们。
  • @iammilind:这就是所谓的“不要为不使用的东西付费”。您可以在标准库中看到这种理念已经很普遍,其中您有许多不同的排序和搜索功能,每个功能都针对特定要求和先决条件进行了优化(例如,如果您只寻找前十名,但不是按特定顺序 ->完全分类会浪费处理能力)。您也可以在许多容器类型中看到它。那么,你一定很讨厌 C++ 的标准库。您可以使用其他语言获得帮助,仅提供 List<>full_sort

标签: c++ smart-pointers


【解决方案1】:

回答您的各种问题:

循环引用是指 2 个不同的对象各自具有指向另一个对象的 shared_ptr。

例如:

struct Foo {
     shared_ptr< Bar > m_bar;
};

struct Bar {
     shared_ptr< Foo > m_foo;
};


void createObject()
{
    shared_ptr< Foo > foo( new Foo );
    shared_ptr< Bar > bar( new Bar );
    foo->m_bar = bar;
    bar->m_foo = foo;
    //Neither of these objects will be released here
}

这不会导致两个对象都被释放,因为 Foo 将始终保持对 bar 的引用计数高于 1,而 foo 不会被释放,因为 bar 将始终保持其引用计数高于 1。

这种情况可以通过weak_ptr 来克服,因为它们不会增加引用计数。正如您所指出的,这不会阻止 ptr 被释放,但允许您在使用它之前检查该对象是否仍然存在,这是您无法使用标准指针完成的。

对于您提供的示例,您几乎应该始终使用智能指针而不是原始指针,因为它们允许对象在超出范围时自动释放,而不必确保自己完成,这很容易出错。在您有异常的情况下尤其如此,这很容易跳过您编写的任何发布代码。

例如,这段代码可能会导致问题:

Foo* foo = createObject();
foo.doSomething();
deleteObject( foo );

如果 foo.doSomething 要除外,则永远不会调用 deleteObject 并且 foo 不会被释放。

不过,这样是安全的:

shared_ptr< Foo > foo = createObject();
foo.doSomething();

无论是否发生异常,shared_ptr都会在代码块结束时自动释放。

这里对指针和智能指针进行了相当好的讨论:Pointers, smart pointers or shared pointers?

【讨论】:

  • 您的示例中的任何内容(包含语法错误)都不一定构成循环。这可能是一个非常有效的链表,其中奇数和偶数元素具有不同的类型。
【解决方案2】:

这是一个循环引用的简单示例:

struct Node {
    shared_ptr<Node> next;
};

int main()
{
    shared_ptr<Node> n1(new Node), n2(new Node);
    n1->next = n2;
    n2->next = n1;
}

n1n2 互相指向,所以它们形成了一个循环。 Vanilla shared_ptr 只能与 directed acyclic graphs (DAG) 一起使用。对于循环的,有weak_ptr,它不会在循环面前搞砸引用计数,但应该小心使用。 DAG 或树结构中的反向指针是weak_ptr 的有效用例,允许您在结构中进行备份。

关于你当前的项目:是的,试试shared_ptr,它可能会让你的生活更轻松。您可以使用use_count() &gt;= 2 检查两个容器中是否存在对象;请注意&gt;=2,因为您可能会将指向包含对象的指针分发给客户端代码,这会增加引用计数。

【讨论】:

  • 实际上客户端代码将被引用到 OBJECTS,试图隐藏底层内存模型。
  • "DAG 或树结构中的反向指针是weak_ptr 的有效用例,允许您在结构中备份。" 仅当您可以接受这些反向指针成为随时为空。 IOW,仅当您真的不需要反向指针时,但如果提供了它,则可以使用它。
【解决方案3】:

如果你使用 shared_ptr,你可以得到一个指针圈,例如p1 -​​> p2 -> p3 -> p1,然后他们永远不会被释放。要打破圆圈,您可以使用weak_ptr,例如 p1 sp-> p2 sp-> p3 wp-> p1,然后共享指针就可以自动释放了。

要记住的一点是,即使智能指针使您免于记住显式删除资源,它们也不是灵丹妙药,您仍然可能会出现内存“泄漏”,例如当您有一个指针圈时,以及在复杂的系统中,它们可能同样难以追踪。

【讨论】:

  • "你可以使用weak_ptr来打破循环" weak_ptr没有引用语义,但是弱引用语义。 IOW,weak_ptr 具有完全不同的语义,不能替代 shared_ptr
【解决方案4】:

对于协调两个不同容器的具体问题,一种方法是将两个容器捆绑在一个类中,该类将保持此不变性。

另一个是使用Boost.MultiIndex,它已经提供了这个保证。需要一点练习,我仍然建议使用相关方法包装访问,以便为用户提供以业务为中心的界面。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    • 2010-09-15
    • 2018-12-10
    相关资源
    最近更新 更多