今天有同事问起来关于boost的smart pointer的事情,原因是别人有一段code用了boost::scoped_ptr<>*,review的时候被揪出来说这不符合常理,讨论应该用啥容器。
基本情况就是有一些资源需要new出来放在一个容器里,这个容器的生命周期由自己控制,但是需要把new出来的东西作为一个数组(或者容器)传给别人。
原来的code写成了一个boost::scoped_ptr<>的数组,然后传给别人,像下面这样:
boost::scoped_ptr someResource[number]; for (int i = 0; i < number; ++i) { someResource[i].reset(new T); } OtherFunction(someResource);
很显然,这样的code能编译,能正常工作(只要OtherFunction的实现没问题);只是,真的太不符合common sense了。怎么整?
直觉上来说,既然是一个指针的数组,而且要传给别人,那用std::vector<boost::shared_ptr<T>>最合适了,然后传个const&给别人,搞定。
不过看到瑞典同事有人用boost::ptr_vector,这个新鲜的玩意儿不常见,研究一下,原来是Boost.Pointer Container的一部分,用来保存heap-allocated objects,有放进去的指针会在出了作用域之后自动删除,所以有”own”的语义。
相比起shared_ptr的容器,有各种优点(见上面link里的advantages 1~8)。
当然,最主要的是语义上的不同:
- boost::ptr_vector保存的是“own”的对象;
- std::vector<boost::shared_ptr<>>保存的对象可以被别人own
然后,从效率上来说,ptr_vector显然要更好一点,因为创建shared_ptr还是有开销的。
回到上面的case,最简单的做法就是用shared_ptr的容器;更合适的做法是用ptr_vector。
那么,它们的效率到底能差多少呢?写段code跑跑看。
#include <boost/ptr_container/ptr_vector.hpp> #include <boost/shared_ptr.hpp> #include <vector> #include <cstdio> #include <ctime> #include <stdint.h> class TSomeData { private: int data; public: TSomeData(int d) : data(d) { // Empty } }; const int TEST_ITERATIONS = 10000000; typedef std::vector<boost::shared_ptr > TVectorOfShared; typedef boost::ptr_vector TPtrVector; int main() { clock_t start; clock_t end; start = ::clock(); TVectorOfShared vectorOfShared; for (int i = 0; i < TEST_ITERATIONS; ++i) { // Test vector of shared_ptr boost::shared_ptr data(new TSomeData(i)); vectorOfShared.push_back(data); } end = ::clock(); printf("Vector of shared:\n Time executed: %u\n", static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000))); start = ::clock(); TPtrVector ptrVector; for (int i = 0; i < TEST_ITERATIONS; ++i) { // Test ptr_vector TSomeData* data = new TSomeData(i); ptrVector.push_back(data); } end = ::clock(); printf("PtrVector:\n Time executed: %u\n", static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000))); return 0; }
跑一下结果如下(老式T400,跑着Ubuntu 14.04).
# g++ -O0 Vector of shared: Time executed: 7227 PtrVector: Time executed: 1507 # g++ -O2 Vector of shared: Time executed: 5090 PtrVector: Time executed: 731
无论是-O0还是-O2,都有着明显的差距。
结论:在语义合适的情况下,用ptr_vector有更好的效率。
最后,在stackoverflow上看到,如果项目的编译环境已经用c++11了,可以用std::vector<std::unique_ptr<T>>,测试一下,结果有点吃惊。
首先,unique_ptr相关的测试code如下:
for (int i = 0; i < TEST_ITERATIONS; ++i) { std::unique_ptr data(new TSomeData(i)); vectorOfUnique.push_back(std::move(data)); }
测试结果:
# g++ -O0 -std=c++0x Vector of Unique: Time executed: 5057 Vector of shared: Time executed: 4080 PtrVector: Time executed: 1681 # g++ -O2 -std=c++0x Vector of Unique: Time executed: 835 Vector of shared: Time executed: 1794 PtrVector: Time executed: 743
让我吃惊的是:
- 在-O0下,unique_ptr反而是最慢的,不明白(如果有人知道为什么,请告诉我)
- 在-O2下,unique_ptr和ptr_vector有着差不多的效率;但是vector<shared_ptr>相比没有c++0x的时候效率也提升了很多!
结论:
- 在合适的语义下,ptr_vector最好(好吧,我更习惯用boost…)
- 能体会到,c++0x在标准库下有着更好的效率,至于是不是适合项目使用,看项目情况吧
- 在不用考虑效率的时候(个人觉得,开发中不需要在执行效率上太抠,把优化留到实际使用发现性能之后),vector<shared_ptr>最万能。
Q.E.D