有哪些方法可以在使用时射中自己的脚boost::shared_ptr
?换句话说,我在使用时必须避免哪些陷阱boost::shared_ptr
?
13 回答
循环引用:a指向对原始对象shared_ptr<>
具有 a 的东西。shared_ptr<>
当然,你可以用它weak_ptr<>
来打破这个循环。
我添加以下内容作为我在评论中谈论的示例。
class node : public enable_shared_from_this<node> {
public :
void set_parent(shared_ptr<node> parent) { parent_ = parent; }
void add_child(shared_ptr<node> child) {
children_.push_back(child);
child->set_parent(shared_from_this());
}
void frob() {
do_frob();
if (parent_) parent_->frob();
}
private :
void do_frob();
shared_ptr<node> parent_;
vector< shared_ptr<node> > children_;
};
在这个例子中,你有一个节点树,每个节点都有一个指向其父节点的指针。frob() 成员函数,无论出于何种原因,都会在树中向上波动。(这并不完全古怪;一些 GUI 框架以这种方式工作)。
问题是,如果你失去了对最顶层节点的引用,那么最顶层节点仍然持有对其子节点的强引用,并且它的所有子节点也持有对其父节点的强引用。这意味着存在循环引用使所有实例无法自行清理,而无法从代码中实际到达树,这会导致内存泄漏。
class node : public enable_shared_from_this<node> {
public :
void set_parent(shared_ptr<node> parent) { parent_ = parent; }
void add_child(shared_ptr<node> child) {
children_.push_back(child);
child->set_parent(shared_from_this());
}
void frob() {
do_frob();
shared_ptr<node> parent = parent_.lock(); // Note: parent_.lock()
if (parent) parent->frob();
}
private :
void do_frob();
weak_ptr<node> parent_; // Note: now a weak_ptr<>
vector< shared_ptr<node> > children_;
};
在这里,父节点已被弱指针替换。它不再对它所指的节点的生命周期有发言权。因此,如果最上面的节点超出了前面示例中的范围,那么虽然它持有对其子节点的强引用,但它的子节点并不持有对其父节点的强引用。因此没有对该对象的强引用,它会自行清理。反过来,这会导致孩子们失去他们的一个强参考,这导致他们清理,等等。简而言之,这不会泄漏。只需战略性地将 shared_ptr<> 替换为 weak_ptr<>。
注意:上述内容同样适用于 std::shared_ptr<> 和 std::weak_ptr<>,就像它适用于 boost::shared_ptr<> 和 boost::weak_ptr<> 一样。
shared_ptr
为同一个对象创建多个不相关的 's:
#include <stdio.h>
#include "boost/shared_ptr.hpp"
class foo
{
public:
foo() { printf( "foo()\n"); }
~foo() { printf( "~foo()\n"); }
};
typedef boost::shared_ptr<foo> pFoo_t;
void doSomething( pFoo_t p)
{
printf( "doing something...\n");
}
void doSomethingElse( pFoo_t p)
{
printf( "doing something else...\n");
}
int main() {
foo* pFoo = new foo;
doSomething( pFoo_t( pFoo));
doSomethingElse( pFoo_t( pFoo));
return 0;
}
构造一个匿名临时共享指针,例如在函数调用的参数中:
f(shared_ptr<Foo>(new Foo()), g());
这是因为允许new Foo()
执行,然后g()
调用并g()
抛出异常,而无需shared_ptr
设置,因此shared_ptr
没有机会清理Foo
对象。
小心制作两个指向同一个对象的指针。
boost::shared_ptr<Base> b( new Derived() );
{
boost::shared_ptr<Derived> d( b.get() );
} // d goes out of scope here, deletes pointer
b->doSomething(); // crashes
而是使用这个
boost::shared_ptr<Base> b( new Derived() );
{
boost::shared_ptr<Derived> d =
boost::dynamic_pointer_cast<Derived,Base>( b );
} // d goes out of scope here, refcount--
b->doSomething(); // no crash
此外,任何持有 shared_ptrs 的类都应该定义复制构造函数和赋值运算符。
不要尝试在构造函数中使用 shared_from_this() ——它不起作用。而是创建一个静态方法来创建类并让它返回一个 shared_ptr。
我已经毫无问题地传递了对 shared_ptrs 的引用。只要确保它在保存之前被复制(即,没有作为类成员的引用)。
这里有两件事要避免:
调用该
get()
函数以获取原始指针并在指向的对象超出范围后使用它。将 a 的引用或原始指针传递给 a
shared_ptr
也应该是危险的,因为它不会增加有助于保持对象存活的内部计数。
我们调试了几个星期的奇怪行为。
原因是:
我们将 'this' 传递给了一些线程工作者,而不是 'shared_from_this'。
不完全是一支足球枪,但肯定是令人沮丧的根源,直到您围绕如何以 C++0x 方式进行操作:您所知道和喜爱的大多数谓词<functional>
都不能很好地与shared_ptr
. 令人高兴的是,std::tr1::mem_fn
它可以与对象、指针和s 一起使用,shared_ptr
替换std::mem_fun
,但如果您想使用std::negate
,或任何与 的老朋友,请准备好适应并可能使用参数占位符。实际上,这实际上要通用得多,因为现在您基本上最终都会使用每个函数对象适配器,但是如果您已经熟悉 STL 的便利函数,则需要一些时间来适应。std::not1
std::plus
shared_ptr
std::tr1::bind
bind
如果堆上有很多小对象但它们并不是真正“共享”的,那么使用shared_ptr
非常小的对象(如)可能会产生开销。为它在 g++ 4.4.3 和带有 Boost 1.42 的 VS2008 上创建的每个新引用计数分配 16 个字节。分配 20 个字节。现在,如果您有一百万个不同,这意味着您的内存中有 2000 万字节只保留了 count=1。更不用说间接成本和内存碎片了。在您最喜欢的平台上尝试以下操作。char
short
boost::shared_ptr
std::tr1::shared_ptr
shared_ptr<char>
void * operator new (size_t size) {
std::cout << "size = " << size << std::endl;
void *ptr = malloc(size);
if(!ptr) throw std::bad_alloc();
return ptr;
}
void operator delete (void *p) {
free(p);
}
在类定义中为此提供 shared_ptr< T > 也是危险的。请改用 enabled_shared_from_this。
在此处查看以下帖子
shared_ptr
在多线程代码中使用时需要小心。shared_ptr
当几个s 指向同一个内存,被不同的线程使用时,就比较容易陷入这种情况。
shared_ptr 的流行广泛使用几乎不可避免地会导致不必要的和看不见的内存占用。
循环引用是众所周知的原因,其中一些可能是间接的且难以发现,尤其是在由多个程序员编写的复杂代码中;程序员可能决定一个对象需要引用另一个对象作为快速修复,并且没有时间检查所有代码以查看他是否正在关闭一个循环。这种危险被大大低估了。
不太为人所知的是未发布参考的问题。如果一个对象被共享给许多 shared_ptrs,那么它不会被销毁,直到它们中的每一个都归零或超出范围。很容易忽略其中一个引用,并最终得到潜伏在内存中看不见的对象,而你认为你已经完成了。
虽然严格来说这些不是内存泄漏(它将在程序退出之前全部释放),但它们同样有害且更难检测。
这些问题是权宜之计错误声明的后果: 1. 将您真正想要的单一所有权声明为 shared_ptr。scoped_ptr 是正确的,但是对该对象的任何其他引用都必须是原始指针,它可能会悬空。2. 将你真正想要的被动观察引用声明为 shared_ptr。weak_ptr 是正确的,但是每次你想使用它时,你都会很麻烦地将它转换为 share_ptr。
我怀疑你的项目是这种做法会给你带来的麻烦的一个很好的例子。
如果您有一个内存密集型应用程序,您确实需要单一所有权,以便您的设计可以明确控制对象的生命周期。
单一所有权 opObject=NULL; 肯定会删除该对象,它现在会这样做。
共享所有权 spObject=NULL; ........谁知道?......
如果您有共享对象的注册表(例如,所有活动实例的列表),则这些对象将永远不会被释放。解决方案:在循环依赖结构的情况下(参见 Kaz Dragon 的回答),酌情使用weak_ptr。
智能指针不是万能的,原始指针不能被淘汰
可能最严重的危险是,既然shared_ptr
是一个有用的工具,人们就会开始把它放在任何地方。由于普通指针可能被滥用,同样的人会寻找原始指针并尝试用字符串、容器或智能指针替换它们,即使它没有意义。原始指针的合法使用将变得可疑。会有指针警察。
这不仅可能是最严重的危险,而且可能是唯一的严重危险。所有最严重的滥用都shared_ptr
将是智能指针优于原始指针(无论这意味着什么)的想法的直接后果,并且将智能指针放在任何地方将使 C++ 编程“更安全”。
当然,智能指针需要转换为原始指针才能使用这一事实驳斥了智能指针邪教的这种说法,但原始指针访问是“隐式” in operator*
,operator->
(或显式 in get()
)这一事实,但是在隐式转换中没有隐式,足以给人这样的印象,即这不是真正的转换,并且这种非转换产生的原始指针是无害的临时性。
C++ 不能成为“安全语言”,C++ 的任何有用子集都不是“安全的”
当然,对 C++ 的安全子集(严格意义上的“内存安全”的“安全”,如 LISP、Haskell、Java...)的追求注定是无止境的和不满足的,因为 C++ 的安全子集很小而且几乎没用,因为不安全的原语是规则而不是例外。C++ 中严格的内存安全意味着没有指针,只有自动存储类的引用。但是在程序员被定义信任的语言中,有些人会坚持使用一些(原则上)防白痴的“智能指针”,即使与原始指针相比没有其他优势,即一种特定的方式来破坏程序状态被避免。