关于shared_ptr
shared_ptr,顾名思义,是共享指针。boost库引入shared_ptr为了实现在C++中通过引用计数对堆对象进行内存管理的一种机制。更多关于shared_ptr的信息,可参考shared_ptr template.
一般可以通过以下方式使用shared_ptr:
boost::shared_ptr<classa> sp1(new classa());
boost::shared_ptr<classa> sp2 = sp1;
shared_ptr对象最初创建时的reference count为1,表示当前存在一个指向对象的引用;将sp1通过赋值操作符(=)赋值给sp2后,shared_ptr对象的reference count则会增1,表明又多了一个指向对象的引用。
当推出shared_ptr对象所在的作用域(函数体,代码块,或者是用作返回值或传值参数的临时对象)时,shared_ptr的析构函数就会被调用,此时reference count就会减1,当reference count减到0时才会delete所引用的内存对象。如此便实现了对内存对象的自动管理,程序员不在用去担心何时去delete一个对象,delete一个对象会不会导致一个dangling指针,这些shared_ptr都帮你搞定了。
然而,但凡C/C++中涉及到释放内存资源的一类问题,都要小心使用,否则尽管有shared_ptr,仍可能出现一些隐藏的bug,比如在使用shared_ptr作为参数传递时。
传值还是传引用?
C/C++的函数参数传递有两种方式:传值(pass-by-value)和传引用(pass-by-reference).
void pass_by_value(t t);
pass_by_value(t0);
void pass_by_reference(const t& t);
void pass_by_reference2(t& t);
传值方式会带来一次额外的对象拷贝构造函数调用开销,在pass_by_value中,编译器会调用T的拷贝构造函数从t0构造出一个新的临时的T对象用作pass_by_value的参数,这个对象在函数pass_by_value函数调用结束后会自动析构。
而传引用方式不存在这次额外的构造函数调用,可以简单的理解为传了一个地址作为pass_by_reference的参数。
当构造一个T对象开销很大时,显然,传引用方式具有明显的性能优势。
对于shared_ptr对象,究竟哪种方式更好呢?
void pass_shared_ptr(boost::shared_ptr<t> t);
// case 2: pass by reference
void pass_shared_ptr(const boost::shared_ptr<t>& t){
...
t->dosomething();
}
两种情况的区别正如上述,第一种情况多了一次额外的shared_ptr对象拷贝构造函数的调用。 乍一看,还是传引用的好,但事实并非如此。
使用shared_ptr对象隐含一个假设,就是在shared_ptr对象的作用范围中,它一定指向一个存在并有效的内存对象,也就是说它的引用计数一定为1。在传引用的情况下,如果...代码中对shared_ptr对象有副作用时,比如传入的t是一个对象的成员变量而这个对象又正好被释放了,那么这个shared_ptr的引用计数可能被减为0从而导致所指对象的释放,然后悲剧就发生了,t->doSomething(), 企图在t上解引用去调用一个方法将会导致segment falut。但这毕竟是一个由于code不小心而引入的意外,如果你能确保...代码块中没有对t的副作用,传引用也不会存在大问题。然而,防范于未然应当是每个程序员的编程习惯。
再说说传值,额外多出的一次对象拷贝正是保证了在shared_ptr对象的作用域中它一定能指向一个有效的内存对象这一假设,而shared_ptr对象的拷贝算不算上是重量级的。这个优化实际上没有多大意义。
所以,传递shared_ptr参数还是用传值更好!
总结
总之,还是一句话,C++操作内存对象时要多一个心眼,哪怕是有一些高级的工具帮助我们简化这些工作。