介绍 C++ 的智能指针 (Smart Pointers) 相关 API。

C++ 中的智能指针是为了解决内存泄漏、重复释放等问题而提出的,它基于 RAII (Resource Acquisition Is Initialization),也称为“资源获取即初始化” 的思想实现。智能指针实质上是一个类,但经过封装之后,在行为语义上的表现像指针。

参考资料:

shared_ptr

shared_ptr 能够记录多少个 shared_ptr 共同指向一个对象,从而消除显式调用 delete,当引用计数变为零的时候就会将对象自动删除。

注意,这里使用 shared_ptr 能够实现自动 delete ,但是如果使用之前仍需要 new 的话,代码风格就会变得很「奇怪」,因为 new/delete 总是需要成对出现的,所以尽可能使用封装后的 make_shared 来「代替」new

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 的发生。

内部实现

如下图所示,shared_ptr 内部仅包括 2 个指针,一个指针指向共享对象,另外一个指针指向 Control block .

初始化

  1. 通过构造函数初始化(废话)

下面是正确的方式。

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 这个地址,但这个地址在栈上(而不是堆),因此会发生运行时错误。

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;
}
  1. 如果通过 nullptr 初始化,那么引用计数的初始值为 0 而不是 1 。
shared_ptr<void *> p(nullptr);
cout << p.use_count() << endl;
  1. 不允许通过一个原始指针初始化多个 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
  1. 通过 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 的内容。

自定义 deleter

在初始化时传入一个函数指针,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 指向给定对象。发生下列情况之一时,指定对象就会被释放:

  • 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

自定义 deleter

unique_ptrdeletershared_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

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 要注意避免循环引用

[CPP] 智能指针的更多相关文章

  1. cpp智能指针

    weak_ptr<Cls1> wp1; { shared_ptr<Cls1> ptr1(new Cls1);//共享指针 wp1 = ptr1;//临时共享指针 std::co ...

  2. Android智能指针sp wp详解

    研究Android的时候,经常会遇到sp.wp的东西,网上一搜,原来是android封装了c++中对象回收机制.说明:1. 如果一个类想使用智能指针,那么必须满足下面两个条件:    a. 该类是虚基 ...

  3. 根据OSG中的ref_ptr和Reference简化的智能指针

    main.cpp测试代码 #include "TestSmartPointer" void fun() { SP<TestSmartPointer> sp1=new T ...

  4. C++中智能指针的设计和使用

    转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7561235 智能指针(smart pointer)是存储指向动态分配(堆 ...

  5. auto_ptr,shared_ptr 智能指针的使用

    Q: 那个auto_ptr是什么东东啊?为什么没有auto_array?A: 哦,auto_ptr是一个很简单的资源封装类,是在<memory>头文件中定义的.它使用“资源分配即初始化”技 ...

  6. Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6786239 Android 系统的运行时库层代 ...

  7. 关于智能指针auto_ptr

    智能指针auto_ptr和shared_ptr也是面试中经常被问到的一个 感觉看auto_ptr的源码反而更加容易理解一些,因为源码的代码量并不大,而且比较容易理解. 本篇主要介绍auto_ptr 其 ...

  8. STL模板_智能指针概念

    一.智能指针1.类类型对象,在其内部封装了一个普通指针.当智能指针对象因离开作用域而被析构时,其析构函数被执行,通过其内部封装的普通指针,销毁该指针的目标对象,避免内存泄露.2.为了表现出和普通指针一 ...

  9. Android系统智能指针的设计思路(轻量级指针、强指针、弱指针)

    本博客为原创,转载请注明出处,谢谢. 参考博文:Android系统的智能指针(轻量级指针.强指针和弱指针)的实现原理分析 C++中最容易出错的地方莫过于指针了,指针问题主要有两类,一是内存泄露,二是无 ...

随机推荐

  1. sqoop进行将Hive 词频统计的结果数据传输到Mysql中

    使用sqoop进行将Hive 词频统计的结果数据传输到Mysql中. mysql准备接受数据的数据库与表 hive准备待传输的数据 sqoop进行数据传输  mysql查看传输结果     二:电子书 ...

  2. JS怎么把for循环出来的东西放到一个数组里

    var students=[ {name: "vehicleTravelLicenseCopyBack", id: "a1"}, {name: "ve ...

  3. Spring框架之jms源码完全解析

    Spring框架之jms源码完全解析 我们在前两篇文章中介绍了Spring两大核心IOC(Inversion of Control控制反转)和AOP(Aspect Oriented Programmi ...

  4. 我用go-zero开发了第一个线上项目

    作者:结冰 前言 ​ 说在最前面,我是一个外表谦让,内心狂热,外表斯文,内心贪玩的一个普通人.我的职业是程序员,是一个golang语言爱好者,一半是因为golang好用,一半是因为其他语言学不好.我是 ...

  5. create-react-app 基于TS的项目

    写在前面 最近在用React,发现百度了很多都没有找到基于TS的React项目搭建,很多是老的方法已经属于不成功的了,今天我把最新的搭建基于ts的React的项目分享出来 create-react-a ...

  6. Python高级语法-私有化-私有化理解(4.3.1)

    @ 目录 1.说明 2.代码 关于作者 1.说明 __a(私有):只能在类的内部使用,对象使用,但是子类不允许使用,不能导入到其他包 a(protected):可以在子类使用,可以通过对象访问,不能导 ...

  7. Python高级语法-深浅拷贝-总结(4.2.1)

    @ 目录 1.说明 2.代码 关于作者 1.说明 任何可变数据类型都牵扯到深浅拷贝 但是元组,常数等,不可变数据类型,无论浅拷贝,深拷贝都是指向 不管如何嵌套,一旦牵扯到可变数据类型,都会有深浅区别 ...

  8. Hibernate实现对数据的CRUD

    今天主要去看公司的老框架, CRUD用的较多,所以总结一下步骤,以免忘记的时候温习 回顾 JDBC 工作过程: 加载驱动 建立连接 定义sql,发生sql语句 执行sql语句获得执行结果 处理返回结果 ...

  9. BF,BM,KMP,就这?

    为保证代码严谨性,文中所有代码均在 leetcode 刷题网站 AC ,大家可以放心食用. 皇上生辰之际,举国同庆,袁记菜馆作为天下第一饭店,所以被选为这次庆典的菜品供应方,这次庆典对于袁记菜馆是一项 ...

  10. java 多线程40个问题汇总(转)

    java 多线程40个问题汇总,自己也记录一份,如有侵权,联系删除 ref from :http://www.cnblogs.com/xrq730/p/5060921.html 1.多线程作用 - 利 ...