c++smart pointer 分析: unique_ptr, shared_ptr & weak_ptr
unqique_ptr
提供对对象的单独唯一引用,可用构造函数创建(c++14支持std::make_unique, 在c++11中没有仅仅是因为忘了。。[4])
shared_ptr
提供对对象的共享引用,可用构造函数或std::make_shared(c++11)创建;
weak_ptr
首先,weak_ptr有几个特点:
(1) 增加weak_ptr不会增加shared_ptr的引用计数,其use_count方法返回的是shared_ptr的引用计数;
(2) 不能通过weak_ptr来使用实际的对象(除非借助lock方法来生成shared_ptr对象),但可以借助它来确认指向的对象是否已经被delete掉了(expired方法);
(3) 从weak_ptr的constructor可以看出,weak_ptr不能脱离shared_ptr而单独存在,也就是说,weak_ptr必须从shared_ptr或其他weak_ptr生成。 weak_ptr主要用于以下场景:
(1) 解决循环引用问题: 这个应该是最广泛使用的场景了吧,在循环引用条件下,必须使用weak_ptr。而且一般两个对象一个是owner,一个是owned,owner里使用shared_ptr,owned里使用weak_ptr[2];
(2)与shared_ptr结合使用,使用shared_ptr来管理数据,而使用weak_ptr来给数据的实际使用者,利用expired和lock方法,可以方便使用数据,并确定shared_ptr指向的实际数据是否已经被delete掉,当需要使用shared_ptr的时候,用lock方法返回一个shared_ptr[1,2]。“std::weak_ptr models temporary ownership: when an object needs to be accessed only if it exists, and it may be deleted at any time by someone else, std::weak_ptr is used to track the object, and it is converted to std::shared_ptr to assume temporary ownership. If the original std::shared_ptr is destroyed at this time, the object’s lifetime is extended until the temporary std::shared_ptr is destroyed as well”[1]
smart pointer 和 raw pointer的性能对比
参考博客[5],先说下博客结论: There are only few reasons in modern C++ justifying the memory management with new and delete. 自己写了测试代码实际测了下,代码如下[6] (https://github.com/cothee/interesting_test): test.h :
//test.h
#include <chrono>
#include <iostream>
#define TEST_NUM 1000000
#define TEST_FUN1(op1, fun_name) {int count = 0; \
auto start = std::chrono::high_resolution_clock::now(); \
do {op1;} while (++count < TEST_NUM); \
auto end =std::chrono::high_resolution_clock::now(); \
std::chrono::duration<double,std::nano> elapsed = end - start; \
std::cout << fun_name << " use: " << elapsed.count() / TEST_NUM << " nanoseconds" << std::endl; \
}
#define TEST_FUN2(op1, op2, fun_name) {int count = 0; \
auto start = std::chrono::high_resolution_clock::now(); \
do {op1; op2;} while (++count < TEST_NUM); \
auto end =std::chrono::high_resolution_clock::now(); \
std::chrono::duration<double,std::nano> elapsed = end - start; \
std::cout << fun_name << " use: " << elapsed.count() / TEST_NUM << " nanoseconds" << std::endl; \
test.cc :
#include <iostream>
#include <memory>
#include "test.h"
struct Node {
int a;
int b;
int c;
Node(int m, int n, int p) {
a = m;
b = n;
c = p;
}
};
void test_shared() {
TEST_FUN1(std::shared_ptr<Node> ptr(new Node(1,2,3)), "new shared_ptr");
}
void test_unique() {
TEST_FUN1(std::unique_ptr<Node> ptr(new Node(1,2,3)), "new unique_ptr");
}
void test_raw() {
TEST_FUN2(Node* n = new Node(1,2,3), delete n, "new raw_pointer");
}
void test_malloc() {
Node* p;
TEST_FUN2(p = (Node*)malloc(sizeof(Node)), free(p), "malloc & free");
}
void copy_shared() {
std::shared_ptr<Node> ptr_ori;
TEST_FUN1(auto ptr = ptr_ori, "copy shared_ptr");
}
void copy_weaked() {
std::shared_ptr<Node> ptr_ori;
TEST_FUN1(std::weak_ptr<Node> ptr = ptr_ori, "copy weak_ptr from shared_ptr");
}
int main() {
test_shared();
test_unique();
test_raw();
test_malloc();
copy_shared();
copy_weaked();
}
测试结果:
operation | optimization level | time(nanoseconds) |
---|---|---|
new shared_ptr | O0 | 102.5 |
new unique_ptr | O0 | 76.9 |
new raw_ptr | O0 | 28.6 |
malloc | O0 | 23.6 |
copy shared_ptr | O0 | 17.3 |
new weak_ptr from shared_ptr | O0 | 17.4 |
new shared_ptr | O1 | 53.1 |
new unique_ptr | O1 | 26.1 |
new raw_ptr | O1 | 25.6 |
malloc | O1 | 0.31 |
copy shared_ptr | O1 | 0.31 |
new weak_ptr from shared_ptr | O1 | 0.31 |
new shared_ptr | O2 | 52.9 |
new unique_ptr | O2 | 25.5 |
new raw_ptr | O2 | 25.6 |
malloc | O2 | 4.2e-06 |
copy shared_ptr | O2 | 3.9e-06 |
new weak_ptr from shared_ptr | O2 | 2.9e-06 |
可见,基本上,新创建一个shared_ptr还是比较耗时间的,基本为unique_ptr和 raw new的1.5~2.5倍,但是shared_ptr和weak_ptr的copy基本不花多少时间。需要注意的是,在加优化的情况下,malloc还是比new更加快的,而且不只一个数量级。所以,个人理解,如果一定要用raw pointer,new还不如malloc来的更快,当然这就需要以舍弃c++的一些特性作为代价了。但是另一方面也要想到,创建10,000,000个shared_ptr也只需要花费大概0.5~1s,而且实际中也不可能一上来就创建这么多shared_ptr,肯定是在代码运行过程中逐渐创建的。所以,实际中真的需要以舍弃c++的方便特性来换取这种提升吗?感觉意义不是很大。
references:
[1]https://en.cppreference.com/w/cpp/memory/weak_ptr
[2]https://stackoverflow.com/questions/12030650/when-is-stdweak-ptr-useful
[3]https://stackoverflow.com/questions/5671241/how-does-weak-ptr-work/5671308#5671308
[4]https://herbsutter.com/gotw/_102/
[5]http://www.modernescpp.com/index.php/memory-and-performance-overhead-of-smart-pointer
[6]https://github.com/cothee/interesting_test