介绍 C++ 的智能指针 (Smart Pointers) 相关 API。ios
C++ 中的智能指针是为了解决内存泄漏、重复释放等问题而提出的,它基于 RAII (Resource Acquisition Is Initialization),也称为“资源获取即初始化” 的思想实现。智能指针实质上是一个类,但通过封装以后,在行为语义上的表现像指针。数组
参考资料:安全
shared_ptr
可以记录多少个 shared_ptr
共同指向一个对象,从而消除显式调用 delete
,当引用计数变为零的时候就会将对象自动删除。多线程
注意,这里使用 shared_ptr
可以实现自动 delete
,可是若是使用以前仍须要 new
的话,代码风格就会变得很「奇怪」,由于 new/delete
老是须要成对出现的,因此尽量使用封装后的 make_shared
来「代替」new
。ide
shared_ptr
基于引用计数实现,每个 shared_ptr
的拷贝均指向相同的内存。若是某个 shared_ptr
被析构(生命周期结束),那么引用计数减 1 ,当引用计数为 0 时,自动释放指向的内存。函数
shared
的全部成员函数,包括拷贝构造函数 (Copy Constructor) 和拷贝赋值运算 (Copy Assignment Operator),都是线程安全的,即便这些 shared_ptr
指向同一对象。但若是是多线程访问同一个 non-const 的 shared_ptr
,那有可能发生资源竞争 (Data Race) 的状况,好比改变这个 shared_ptr
的指向,所以这种状况须要实现多线程同步机制。固然,可使用 shared_ptr overloads of atomic functions 来防止 Data Race 的发生。ui
以下图所示,shared_ptr
内部仅包括 2 个指针,一个指针指向共享对象,另一个指针指向 Control block .this
下面是正确的方式。atom
void func1() { int *a = new int[10]; shared_ptr<int[]> p(a); // a is same as p.get() cout << a << endl; cout << p.get() << endl; for (int i = 0; i < 10; i++) p[i] = i; for (int i = 0; i < 10; i++) cout << a[i] << ' '; } // Output: 1-9
下面是错误的方式,由于 ptr
析构时会释放 &a
这个地址,但这个地址在栈上(而不是堆),所以会发生运行时错误。spa
int main() { int a = 10; shared_ptr<int> ptr(&a); // a is same as p.get(), but runs fail cout << &a << endl; cout << ptr.get() << endl; }
nullptr
初始化,那么引用计数的初始值为 0 而不是 1 。shared_ptr<void *> p(nullptr); cout << p.use_count() << endl;
shared_ptr
。int main() { int *p = new int[10]; shared_ptr<int> ptr1(p); shared_ptr<int> ptr2(p); cout << p << endl; cout << ptr1.get() << endl; cout << ptr2.get() << endl; }
上述方式是错误的。能够经过编译,三行 cout
也能正常输出,但会发生运行时错误,由于 ptr2
会先执行析构函数,释放 p
,而后 ptr1
进行析构的时候,就会对无效指针 p
进行重复释放。
0x7feefd405a10 0x7feefd405a10 0x7feefd405a10 a.out(6286,0x113edde00) malloc: *** error for object 0x7feefd405a10: pointer being freed was not allocated a.out(6286,0x113edde00) malloc: *** set a breakpoint in malloc_error_break to debug
make_shared
初始化make_shared
的参数能够时一个对象,也能够是跟该类的构造函数匹配的参数列表。
auto ptr1 = make_shared<vector<int>>(10, -1); auto ptr2 = make_shared<vector<int>>(vector<int>(10, -1));
与经过构造函数初始化不一样的是,make_shared
容许传入一个临时对象,如如下代码:
int main() { vector<int> v = {1, 2, 3}; auto ptr = make_shared<vector<int>>(v); // &v = 0x7ffeef698690 // ptr.get() = 0x7fc03ec05a18 cout << &v << endl; cout << ptr.get() << endl; // v[0] is still 1 ptr.get()->resize(3, -1); cout << v[0] << endl; }
经过 ptr.get()
获取指针并修改指向的内存,并不会影响局部变量 v
的内容。
在初始化时传入一个函数指针,shared_ptr
在释放指向的对象时,会调用自定义的 deleter
处理释放行为。
int main() { int *p = new int[10]; auto func = [](int *p) { delete[] p; cout << "Delete memory at " << p << endl; }; shared_ptr<int> ptr(p, func); }
那么 deleter
有什么用呢?假如咱们有这么一段代码:
class Basic { public: Basic() { cout << "Basic" << endl; } ~Basic() { cout << "~Basic" << endl; } }; int main() { Basic *p = new Basic[3]; shared_ptr<Basic> ptr(p); }
这段代码会发生运行时错误。由于 shared_ptr
默认是使用 delete
去释放指向的对象,但定义了析构函数的对象数组,必需要经过 delete[]
析构,不然产生内存错误。
所以,为了使上述代码正常工做,须要自定义 delete
函数:
shared_ptr<Basic> ptr(p, [](Basic *p){ delete[] p; });
或者(C++17 及其以后的标准支持):
shared_ptr<Base[]> ptr(p);
根据参考资料 [1] ,shared_ptr
指向一个函数,有时用于保持动态库或插件加载,只要其任何函数被 shared_ptr
引用:
void func() { cout << "hello" << endl; } int main() { shared_ptr<void()> ptr(func, [](void (*)()) {}); (*ptr)(); }
注意,这里自定义的 deleter
是必不可少的,不然不能经过编译。
#include <iostream> #include <memory> #include <thread> #include <chrono> #include <mutex> using namespace std; class Base { public: Base() { cout << "Base" << endl; } ~Base() { cout << "~Base" << endl; } }; class Derived : public Base { public: Derived() { cout << " Derived" << endl; } ~Derived() { cout << " ~Derived" << endl; } }; void worker(shared_ptr<Base> ptr) { this_thread::sleep_for(std::chrono::seconds(1)); shared_ptr<Base> lp = ptr; { static std::mutex io_mutex; lock_guard<mutex> lock(io_mutex); cout << "local pointer in a thread:\n" << " lp.get() = " << lp.get() << ", " << " lp.use_count() = " << lp.use_count() << "\n"; } } int main() { shared_ptr<Base> ptr = make_shared<Derived>(); cout << "Created a shared Derived (as a pointer to Base)\n" << "ptr.get() = " << ptr.get() << ", " << "ptr.use_count() = " << ptr.use_count() << '\n'; thread t1(worker, ptr), t2(worker, ptr), t3(worker, ptr); this_thread::sleep_for(std::chrono::seconds(2)); ptr.reset(); std::cout << "Shared ownership between 3 threads and released\n" << "ownership from main:\n" << " p.get() = " << ptr.get() << ", p.use_count() = " << ptr.use_count() << '\n'; t1.join(), t2.join(), t3.join(); }
输出:
Base Derived Created a shared Derived (as a pointer to Base) ptr.get() = 0x7fcabc405a08, ptr.use_count() = 1 Shared ownership between 3 threads and released ownership from main: p.get() = 0x0, p.use_count() = 0 local pointer in a thread: lp.get() = 0x7fcabc405a08, lp.use_count() = 6 local pointer in a thread: lp.get() = 0x7fcabc405a08, lp.use_count() = 4 local pointer in a thread: lp.get() = 0x7fcabc405a08, lp.use_count() = 2 ~Derived ~Base
lp.use_count
也多是 {5,3,2}
这样的序列。在 worker
传入参数过程当中,ptr
被拷贝了 3 次,而且在进入 worker
后,三个线程的局部变量 lp
又把 ptr
拷贝了 3 次,所以 user_count
的最大值是 7 。
unique_ptr
保证同一时刻只能有一个 unique_ptr
指向给定对象。发生下列状况之一时,指定对象就会被释放:
unique_ptr
被销毁(生命周期消亡,被 delete
等状况)unique_ptr
调用 reset
或者进行 ptr1 = move(ptr2)
操做基于这 2 个特色,non-const 的 unique_ptr
能够把管理对象的全部权转移给另一个 unique_ptr
:
示例代码:
class Base { public: Base() { cout << "Base" << endl; } ~Base() { cout << "~Base" << endl; } }; int main() { auto p = new Base(); cout << p << endl; unique_ptr<Base> ptr(p); unique_ptr<Base> ptr2 = std::move(ptr); cout << ptr.get() << endl; cout << ptr2.get() << endl; } /* Output is : Base 0x7fd81fc059f0 0x0 0x7fd81fc059f0 ~Base */
在上述代码中,存在 U = move(V)
,当执行该语句时,会发生两件事情。首先,当前 U 所拥有的任何对象都将被删除;其次,指针 V 放弃了原有的对象全部权,被置为空,而 U 则得到转移的全部权,继续控制以前由 V 所拥有的对象。
若是是 const unique_ptr
,那么其指向的对象的做用域 (Scope) 只能局限在这个 const unique_ptr
的做用域当中。
此外,unique_ptr
不能经过 pass by value 的方式传递参数,只能经过 pass by reference 或者 std::move
。
与 shared_ptr
相似。但因为 unique_ptr
的特色,它没有拷贝构造函数,所以不容许 unique_ptr<int> ptr2 = ptr
这样的操做。
下面是 unique_ptr
正确初始化的例子。
class Base { public: Base() { cout << "Base" << endl; } ~Base() { cout << "~Base" << endl; } void printThis() { cout << this << endl; } }; int main() { auto p = new Base(); unique_ptr<Base> ptr(p); ptr->printThis(); } /* Output is: Base 0x7fbe0a4059f0 ~Base */
int main() { auto p = new Base[3]; unique_ptr<Base[]> ptr(p); for (int i = 0; i < 3; i++) ptr[i].printThis(); } /* Output is: Base * 3 0xc18c28 0xc18c29 0xc18c2a ~Base * 3 */
make_unique
与 make_shared
相似,容许向 make_unique
传入一个临时变量。
void func3() { auto ptr = make_unique<vector<int>>(5, 0); for (int i = 0; i < 5;i++) (*ptr)[i] = i; for (int x : *ptr) cout << x << ' '; } // Output: 0 1 2 3 4
unique_ptr
的 deleter
与 shared_ptr
不一样,它是基于模版参数实现的。
使用仿函数
struct MyDeleter { void operator()(Base *p) { cout << "Delete memory[] at " << p << endl; delete[] p; } }; unique_ptr<Base[], MyDeleter> ptr(new Base[3]); // unique_ptr<Base, MyDeleter> ptr(new Base[3]); // both of them is okay
使用普通函数
unique_ptr<Base[], void (*)(Base * p)> ptr(new Base[3], [](Base *p) { cout << "Delete memory[] at " << p << endl; delete[] p; });
使用 std::function
unique_ptr<Base[], function<void(Base *)>> ptr(new Base[3], [](Base *p) { delete[] p; });
注意到,使用普通函数时,模版参数为 void (*)(Base *p)
,这是一种数据类型,该类型是一个指针,指向一个返回值为 void
, 参数列表为 (Base *p)
的函数,而 void *(Base *p)
则是在声明一个函数(看不懂能够忽略)。
unique_ptr
做为函数参数,只能经过引用,或者 move
操做实现。
下列操做没法经过编译:
void func5(unique_ptr<Base> ptr) {} int main() { unique_ptr<Base> ptr(new Base()); func5(ptr); }
须要改为:
void func5(unique_ptr<Base> &ptr) {} func(ptr);
或者经过 move
转换为右值引用:
void func5(unique_ptr<Base> ptr) { cout << "ptr in function: " << ptr.get() << endl; } int main() { auto p = new Base(); cout << "p = " << p << endl; unique_ptr<Base> ptr(p); func5(move(ptr)); cout << "ptr in main: " << ptr.get() << endl; } /* Output is: Base p = 0xa66c20 ptr in function: 0xa66c20 ~Base ptr in main: 0 */
把 unique_ptr
做为函数返回值,会自动发生 U = move(V)
的操做(转换为右值引用):
unique_ptr<Base> func6() { auto p = new Base(); unique_ptr<Base> ptr(p); cout << "In function: " << ptr.get() << endl; return ptr; } int main() { auto ptr = func6(); cout << "In main: " << ptr.get() << endl; }
函数 | 做用 |
---|---|
release | returns a pointer to the managed object and releases the ownership (will not delete the object) |
reset | replaces the managed object (it will delete the object) |
swap | swaps the managed objects |
get | returns a pointer to the managed object |
get_deleter | returns the deleter that is used for destruction of the managed object |
operator bool | checks if there is an associated managed object (more details) |
operator = | assigns the unique_ptr , support U = move(V) , U will delete its own object |
#include <vector> #include <memory> #include <iostream> #include <fstream> #include <functional> #include <cassert> #include <cstdio> using namespace std; // helper class for runtime polymorphism demo class B { public: virtual void bar() { cout << "B::bar\n"; } virtual ~B() = default; }; class D : public B { public: D() { cout << "D::D\n"; } ~D() { cout << "D::~D\n"; } void bar() override { cout << "D::bar\n"; } }; // a function consuming a unique_ptr can take it by value or by rvalue reference unique_ptr<D> passThrough(unique_ptr<D> p) { p->bar(); return p; } // helper function for the custom deleter demo below void close_file(FILE *fp) { std::fclose(fp); } // unique_ptr-base linked list demo class List { public: struct Node { int data; unique_ptr<Node> next; Node(int val) : data(val), next(nullptr) {} }; List() : head(nullptr) {} ~List() { while (head) head = move(head->next); } void push(int x) { auto t = make_unique<Node>(x); if (head) t->next = move(head); head = move(t); } private: unique_ptr<Node> head; }; int main() { cout << "unique ownership semantics demo\n"; { auto p = make_unique<D>(); auto q = passThrough(move(p)); assert(!p), assert(q); } cout << "Runtime polymorphism demo\n"; { unique_ptr<B> p = make_unique<D>(); p->bar(); cout << "----\n"; vector<unique_ptr<B>> v; v.push_back(make_unique<D>()); v.push_back(move(p)); v.emplace_back(new D()); for (auto &p : v) p->bar(); } cout << "Custom deleter demo\n"; ofstream("demo.txt") << "x"; { unique_ptr<FILE, decltype(&close_file)> fp(fopen("demo.txt", "r"), &close_file); if (fp) cout << (char)fgetc(fp.get()) << '\n'; } cout << "Linked list demo\n"; { List list; for (long n = 0; n != 1000000; ++n) list.push(n); cout << "Pass!\n"; } }
weak_ptr
指针一般不单独使用(由于没有实际用处),只能和 shared_ptr
类型指针搭配使用。
当 weak_ptr
类型指针的指向和某一 shared_ptr
指针相同时,weak_ptr
指针并不会使所指堆内存的引用计数加 1;一样,当 weak_ptr
指针被释放时,以前所指堆内存的引用计数也不会所以而减 1。也就是说,weak_ptr
类型指针并不会影响所指堆内存空间的引用计数。
此外,weak_ptr
没有重载 *
和 ->
运算符,所以 weak_ptr
只能访问所指的堆内存,而没法修改它。
weak_ptr
做为一个 Observer 的角色存在,能够获取 shared_ptr
的引用计数,能够读取 shared_ptr
指向的对象。
成员函数:
函数 | 做用 |
---|---|
operator = | weak_ptr 能够直接被 weak_ptr 或者 shared_ptr 类型指针赋值 |
swap | 与另一个 weak_ptr 交换 own objetc |
reset | 置为 nullptr |
use_count | 查看与 weak_ptr 指向相同对象的 shared_ptr 的数量 |
expired | 判断当前 weak_ptr 是否失效(指针为空,或者指向的堆内存已经被释放) |
lock | 若是 weak_ptr 失效,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。 |
例子:
#include <memory> #include <iostream> using namespace std; // global weak ptr weak_ptr<int> gw; void observe() { cout << "use count = " << gw.use_count() << ": "; if (auto spt = gw.lock()) cout << *spt << "\n"; else cout << "gw is expired\n"; } int main() { { auto sp = make_shared<int>(233); gw = sp; observe(); } observe(); } // Output: // use count = 1: 233 // use count = 0: gw is expired
使用智能指针的几个重要原则是:
shared_ptr
要注意避免循环引用