“原始”指针不受管理。即,以下行:
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_ptr
和 weak_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<>
同一指针的所有对象的引用计数。当创建一个新的时,计数增加。当一个被销毁时,计数减少。当计数达到零时,指针为delete
d。
因此,这就带来了一个问题:双向链接结构最终以循环引用为结尾。假设我们要添加一个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
,则会有一个循环引用。它永远不会是delete
d,因为其引用计数永远不会为零。
要解决此问题,请使用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_shared
和 make_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具有其自己的垃圾收集模型:许多QObject
s具有一种可以适当销毁它们而不需要用户使用的机制delete
。
我不知道QObject
当用C ++ 11托管指针管理时s的行为,所以我不能说这shared_ptr<QDialog>
是一个好主意。我没有足够的Qt经验可以肯定地说,但是我相信 Qt5已经针对此用例进行了调整。