介绍 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. react项目中对dom元素样式修改的另一种方法以及将组件插入到node节点中

    在项目中,以前如果遇到对dom元素的操作都是直接获取dom元素,比如说: 但是如果修改的样式比较多的话,不如直接"切换"dom元素,如下例子: 这样会节省一些性能.因为操作dom的 ...

  2. react项目中的一些配置

    react中事件优化使用babel插件 npm install babel-plugin-react-scope-binding --save-dev react中绝对路径引入文件:在根目录下增加js ...

  3. 如何更简单的使用Polly

    Polly 弹性瞬时错误处理库 Polly是一个C#实现的弹性瞬时错误处理库 它可以帮助我们做一些容错模式处理,比如: 超时与重试(Timeout and Retry) 熔断器(Circuit Bre ...

  4. rhel 7 multipath服务启动报错

    配置多路径服务,启动多路径multipathd.service的时候出现下面报错: [root@rac2 ~]# systemctl status multipathd.service multipa ...

  5. jmeter接口测试笔记

    1.接口测试基础 API:Application Programming Interface,即调用应用程序的通道. 接口测试遵循点 接口的功能性实现:检查接口返回的数据与预期结果的一致性. 测试接口 ...

  6. react第十五单元(react路由的封装,以及路由数据的提取)

    第十五单元(react路由的封装,以及路由数据的提取) #课程目标 熟悉react路由组件及路由传参,封装路由组件能够处理路由表 对多级路由能够实现封装通用路由传递逻辑,实现多级路由的递归传参 对复杂 ...

  7. antdv的Upload组件实现前端压缩图片并自定义上传功能

    Ant Design of Vue的Upload组件有几个重要的api属性: beforeUpload: 上传文件之前的钩子函数,支持返回一个Promise对象. customRequest: 覆盖组 ...

  8. HCIP --- BGP综合实验

    实验要求: 实验拓扑: 一.配置IP地址 L:代表环回地址(loop back 0) Y:代表业务网段的地址(loop back 1) 二.因为BGP基于IGP之上,给AS 2内配置OSPF 在R2上 ...

  9. Redis入门指导

    前言 本文提供全网最完整的Redis入门指导教程,下面我们从下载Redis安装包开始,一步一步的学习使用. 下载Redis 官网提供的Redis安装包是服务于Linux的,而我们需要在Window下使 ...

  10. Ext CheckBoxGroup使用

    一.效果图展示 我这里主要是为了实现选择周期时间.如周一.周二.周三等 二.界面界面代码 下面就是我实现的代码,包含了界面.数据处理.回填数据等.可能架构的方式,您的代码和我的代码有差异,但是大体就是 ...