在使用 boost::shared_ptr
时,你可能会犯哪些错误呢?换句话说,我在使用 boost::shared_ptr
时需要避免哪些陷阱呢?
循环引用:指向原始对象的shared_ptr<>
的某些内容有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
:
#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;
}
shared_ptr
构造函数之外使用new
。 - rlbond在函数调用的参数中创建一个匿名临时共享指针:
f(shared_ptr<Foo>(new Foo()), g());
这是因为允许执行new Foo()
,然后调用g()
,并且g()
抛出异常,而没有设置shared_ptr
,所以shared_ptr
没有机会清理Foo
对象。
shared_ptr
构造函数和g()
的执行顺序没有被C++标准规定,所以编译器可以随意排列它们。现在市场上有很多编译器,它们都有许多优化选项,并且针对你的代码进行优化可能涉及到许多因素;依赖未定义行为从来都不是一个好主意。我建议阅读Clang开发者关于未定义行为的系列文章:1, 2, 3。 - Brian Campbellstd::make_shared()
,它可以在单个调用中分配对象并将其包装在共享指针中,以避免此问题。 - Brian Campbell小心将两个指针指向同一个对象。
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_ptr 的类都应该定义复制构造函数和赋值运算符。
不要在构造函数中尝试使用 shared_from_this() -- 它不起作用。相反,创建一个静态方法来创建类并返回 shared_ptr。
我已经毫无问题地传递了 shared_ptr 的引用。只要确保在保存之前它被复制了(即,没有引用作为类成员)。
以下是需要避免的两件事情:
调用 get()
函数获取原始指针并在所指向的对象超出作用域后继续使用它。
将引用或原始指针传递给 shared_ptr
也可能很危险,因为它不会增加内部计数,该计数有助于保持对象的生命。
我们调试了数周奇怪的行为。
原因是:
我们给一些线程工作者传递了 'this',而不是 'shared_from_this'。
如果你在堆上有很多小对象,但它们并不真正“共享”,那么使用shared_ptr
来处理像char
short
这样的非常小的对象可能会导致开销。在g++ 4.4.3和Boost 1.42中,boost::shared_ptr
为每个新引用计数分配16字节。std::tr1::shared_ptr
则分配20字节。现在,如果你有一百万个不同的shared_ptr<char>
,那么仅保持count=1就会消耗2000万字节的内存。更不用说间接成本和内存碎片化了。试着在你最喜欢的平台上尝试以下内容。
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
时需要小心。很容易出现这样的情况,即不同线程使用指向相同内存的几个shared_ptr
。