raw,weak_ptr,unique_ptr,shared_ptr等…如何明智地选择它们?


33

C ++中有很多指针,但是老实说,在5年左右的C ++编程中(特别是Qt Framework),我仅使用旧的原始指针:

SomeKindOfObject *someKindOfObject = new SomeKindOfObject();

我知道还有很多其他“智能”指针:

// shared pointer:
shared_ptr<SomeKindofObject> Object;

// unique pointer:
unique_ptr<SomeKindofObject> Object;

// weak pointer:
weak_ptr<SomeKindofObject> Object;

但是我对如何处理它们以及在原始指针比较中它们可以为我提供什么一无所知。

例如,我有这个类头:

#ifndef LIBRARY
#define LIBRARY

class LIBRARY
{
public:
    // Permanent list that will be updated from time to time where
    // each items can be modified everywhere in the code:
    QList<ItemThatWillBeUsedEveryWhere*> listOfUselessThings; 
private:
    // Temporary reader that will read something to put in the list
    // and be quickly deleted:
    QSettings *_reader;
    // A dialog that will show something (just for the sake of example):
    QDialog *_dialog;
};

#endif 

这显然不是穷尽的,但是对于这三个指针中的每一个,可以让它们保持“原始”状态还是应该使用更合适的方法?

第二次,如果雇主会阅读守则,他会严格限制我使用或不使用哪种指针吗?


这个话题似乎非常适合。那是在2008年。而这里的那种指针我使用哪是什么时候?。我相信您可以找到更好的比赛。这些只是我第一次见到
sehe 2015年

将此人视为边界,因为这不仅涉及这些类的概念含义/意图,还涉及其行为和实现的技术细节。由于接受的答案倾向于前者,因此我很高兴将其作为该SO问题的“ PSE版本”。
Ixrec

Answers:


70

“原始”指针不受管理。即,以下行:

SomeKindOfObject *someKindOfObject = new SomeKindOfObject();

如果delete未在适当的时间执行伴随文件,则会泄漏内存。

auto_ptr

为了最小化这些情况,std::auto_ptr<>引入了。但是,由于C ++在2011年标准之前的局限性,因此auto_ptr泄漏内存仍然非常容易。但是,对于有限的情况,例如:

void func() {
    std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
    // do some work
    // will not leak if you do not copy sKOO_ptr.
}

它最弱的用例之一是在容器中。这是因为如果auto_ptr<>制作的副本且未仔细重置旧副本,则容器可能会删除指针并丢失数据。

unique_ptr

作为替代,C ++ 11引入了std::unique_ptr<>

void func2() {
    std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());

    func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}

unique_ptr<>即使在函数之间传递了此类a,也将正确清除它们。它通过在语义上表示指针的“所有权”来实现此目的-“所有者”将其清除。这使其非常适合在容器中使用:

std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();

不像auto_ptr<>unique_ptr<>在这里的行为不俗,并且在vector调整大小时,在vector复制其后备存储时不会意外删除任何对象。

shared_ptrweak_ptr

unique_ptr<>当然,这很有用,但是在某些情况下,您希望代码库的两个部分能够引用同一对象并在其周围复制指针,同时仍要保证适当的清理。例如,使用时,一棵树可能看起来像这样std::shared_ptr<>

template<class T>
struct Node {
    T value;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

在这种情况下,我们甚至可以保留一个根节点的多个副本,并且当根节点的所有副本都被销毁时,树将被正确清理。

之所以shared_ptr<>可行,是因为每个对象不仅保留指向对象的指针,而且还保留引用shared_ptr<>同一指针的所有对象的引用计数。当创建一个新的时,计数增加。当一个被销毁时,计数减少。当计数达到零时,指针为deleted。

因此,这就带来了一个问题:双向链接结构最终以循环引用为结尾。假设我们要添加一个parent指向我们树的指针Node

template<class T>
struct Node {
    T value;
    std::shared_ptr<Node<T>> parent;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

现在,如果我们删除Node,则会有一个循环引用。它永远不会是deleted,因为其引用计数永远不会为零。

要解决此问题,请使用std::weak_ptr<>

template<class T>
struct Node {
    T value;
    std::weak_ptr<Node<T>> parent;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

现在,一切将正常进行,并且删除节点不会将引用保留在父节点上。但是,这会使树的行走更加复杂:

std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();

这样,您可以锁定对节点的引用,并且可以合理地保证在处理该节点时该引用不会消失,因为您坚持使用了该节点shared_ptr<>

make_sharedmake_unique

现在,有一些小问题shared_ptr<>,并unique_ptr<>认为应加以注意。以下两行有问题:

foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());

如果thrower()引发异常,则两行都将泄漏内存。不仅如此,shared_ptr<>它还使引用计数远离它指向的对象,这可能意味着要进行第二次分配。通常这不是可取的。

C ++ 11 std::make_shared<>()和C ++ 14提供std::make_unique<>()了解决此问题的方法:

foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());

现在,在两种情况下,即使thrower()引发异常,也不会发生内存泄漏。另外,make_shared<>()有机会与其托管对象相同的内存空间中创建引用计数,这既可以更快,又可以节省几个字节的内存,同时还为您提供了例外安全保障!

关于Qt的注意事项

但是,应该指出的是,必须支持C ++ 11之前的编译器的Qt具有其自己的垃圾收集模型:许多QObjects具有一种可以适当销毁它们而不需要用户使用的机制delete

我不知道QObject当用C ++ 11托管指针管理时s的行为,所以我不能说这shared_ptr<QDialog>是一个好主意。我没有足够的Qt经验可以肯定地说,但是我相信 Qt5已经针对此用例进行了调整。


1
@Zilators:请注意我对Qt的补充评论。关于是否应该管理所有三个指针的问题的答案取决于Qt对象是否表现良好。
greyfade

2
“都进行单独的分配来保存指针”?不,unique_ptr永远不会分配任何额外的东西,只有shared_ptr必须分配一个引用计数+分配器对象。“两行都会泄漏内存”?不,只有可能,甚至不能保证不良行为。
Deduplicator 2015年

1
@Deduplicator:我的措辞一定不清楚:shared_ptrnewed对象是一个独立的对象-一个单独的分配。它们存在于不同的位置。make_shared能够将它们放置在同一位置,从而提高了缓存的位置。
greyfade

2
@greyfade:Nononono。shared_ptr是一个对象。为了管理一个对象,它必须分配一个(引用计数(弱+强)+破坏者)对象。make_shared允许将其与托管对象作为一个整体分配。unique_ptr不使用这些指针,因此除了确保对象始终由智能指针拥有之外,没有其他优势。顺便说一句,一个可以具有shared_ptr拥有一个底层对象和表示nullptr,或没有自己的和表示非空指针。
Deduplicator 2015年

1
我查看了一下,似乎对a的操作普遍感到困惑shared_ptr:1.它共享某些对象的所有权(由内部动态分配的对象组成,该对象具有弱的引用计数和强的引用计数,以及一个删除器) 。2.它包含一个指针。这两个部分是独立的。make_uniquemake_shared确保已分配的对象安全地放入智能指针中。另外,make_shared允许一起分配所有权对象和托管指针。
Deduplicator
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.