C++智能指针

一、什么是智能指针

智能指针就是帮我们 C++ 程序员管理动态分配的内存的,它会帮助我们自动释放 new 出来的内存,从而避免内存泄漏。

下面就是一个内存泄露的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <string>
#include <memory>

using namespace std;


// 动态分配内存,没有释放就return
void memoryLeak1() {
string *str = new string("动态分配内存!");
return;
}

// 动态分配内存,虽然有些释放内存的代码,但是被半路截胡return了
int memoryLeak2() {
string *str = new string("内存泄露!");

// ......

// 发生某些异常,需要结束函数
if (1) {
return -1;
}
/////////////////////////////////////////////
// 另外,使用try、catch结束函数,也会造成内存泄漏!
/////////////////////////////////////////////

delete str; // 虽然写了释放内存的代码,但是遭到函数中段返回,使得指针没有得到释放
return 1;
}


int main(void) {

memoryLeak1();

memoryLeak2();

return 0;
}

思考:如果我们分配的动态内存都交由有生命周期的对象来处理,那么在对象过期时,让它的析构函数删除指向的内存,这看似是一个 very nice 的方案?

智能指针就是通过这个原理来解决指针自动释放的问题!

  • C++98 提供了 auto_ptr 模板的解决方案
  • C++11 增加 unique_ptrshared_ptrweak_ptr

二、auto_ptr

2.1 用法

auto_ptr 是 C++ 98 定义的智能指针模板,其定义了管理指针的对象,可以将 new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用 delete 来释放内存!

用法:

1
2
3
#include <memory>

auto_ptr<类型> 变量名(new 类型)

例如:

1
2
3
auto_ptr<string> str(new string(“hello world!”));
auto_ptr<vector<int>> ap(new vector<int>());
auto_ptr<int> array(new int[10]);

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Test {
public:
Test() { cout << "Test的构造函数..." << endl; }
~Test() { cout << "Test的析构函数..." << endl; }

int getDebug() { return this->debug; }

private:
int debug = 20;
};

int main(void) {

//Test *test = new Test; // 若不手动delete会导致内存泄露
auto_ptr<Test> test(new Test);

cout << "test->debug:" << test->getDebug() << endl;
cout << "(*test).debug:" << (*test).getDebug() << endl;

return 0;
}

2.2 常用内置函数

  1. get():获取智能指针托管的指针地址

    1
    2
    3
    4
    5
    // 定义智能指针
    auto_ptr<Test> test(new Test);

    Test *tmp = test.get(); // 获取指针返回
    cout << "tmp->debug:" << tmp->getDebug() << endl;

但一般不会这样使用,因为都可以直接使用智能指针去操作,除非有一些特殊情况。

  1. release():取消智能指针对动态内存的托管

    1
    2
    3
    4
    5
    // 定义智能指针
    auto_ptr<Test> test(new Test);

    Test *tmp2 = test.release(); // 取消智能指针对动态内存的托管
    delete tmp2; // 之前分配的内存需要自己手动释放

也就是智能指针不再对该指针进行管理,改由程序员进行管理!

  1. 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉

    1
    2
    3
    4
    5
    6
    // 定义智能指针
    auto_ptr<Test> test(new Test);

    test.reset(); // 释放掉智能指针托管的指针内存,并将其置NULL

    test.reset(new Test()); // 释放掉智能指针托管的指针内存,并将参数指针取代之

reset 函数会将参数的指针(不指定则为 NULL),与托管的指针比较,如果地址不一致,那么就会析构掉原来托管的指针,然后使用参数的指针替代之。然后智能指针就会托管参数的那个指针了。

2.3 使用注意

  1. 尽可能不要将 auto_ptr 变量定义为全局变量或指针

    1
    2
    // 没有意义,全局变量也是一样
    auto_ptr<Test> *tp = new auto_ptr<Test>(new Test);
  2. 除非自己知道后果,不要把auto_ptr 智能指针赋值给同类型的另外一个 智能指针;

    1
    2
    3
    auto_ptr<Test> t1(new Test);
    auto_ptr<Test> t2(new Test);
    t1 = t2; // 不要这样操作...
  3. C++11 后 auto_ptr 已经被“抛弃”,已使用 unique_ptr 替代!C++11 后不建议使用 auto_ptr

原因如下:

  • 复制或者赋值都会改变资源的所有权

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // auto_ptr 被C++11抛弃的主要原因
    auto_ptr<string> p1(new string("I'm Li Ming!"));
    auto_ptr<string> p2(new string("I'm age 22."));

    cout << "p1:" << p1.get() << endl;
    cout << "p2:" << p2.get() << endl;

    // p2赋值给p1后,首先p1会先将自己原先托管的指针释放掉,然后接收托管p2所托管的指针,
    // 然后p2所托管的指针制NULL,也就是p1托管了p2托管的指针,而p2放弃了托管。
    p1 = p2;
    cout << "p1 = p2 赋值后:" << endl;
    cout << "p1:" << p1.get() << endl;
    cout << "p2:" << p2.get() << endl;

    /* output
    p1:0x1071dd0
    p2:0x1071e00
    p1 = p2 赋值后:
    p1:0x1071e00
    p2:0
    */
  • 在 STL 容器中使用 auto_ptr 存在着重大风险,因为容器内的元素必须支持可复制和可赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    vector<auto_ptr<string>> vec;
    auto_ptr<string> p3(new string("I'm P3"));
    auto_ptr<string> p4(new string("I'm P4"));

    // 必须使用std::move修饰成右值,才可以进行插入容器中
    vec.push_back(std::move(p3));
    vec.push_back(std::move(p4));

    cout << "vec.at(0):" << *vec.at(0) << endl;
    cout << "vec[1]:" << *vec[1] << endl;


    // 风险来了:
    vec[0] = vec[1]; // 如果进行赋值,问题又回到了上面一个问题中。
    cout << "vec.at(0):" << *vec.at(0) << endl;
    cout << "vec[1]:" << *vec[1] << endl;

    /* output
    vec.at(0):I'm P3
    vec[1]:I'm P4
    vec.at(0):I'm P4
    vec[1]:
    */
  • 不支持对象数组的内存管理

    1
    auto_ptr<int[]> array(new int[5]);	// 不能这样定义

三、unique_ptr

3.1 特性

特性:

  1. 基于排他所有权模式:两个指针不能指向同一个资源
  2. 无法进行左值 unique_ptr 复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
  3. 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
  4. 在容器中保存指针是安全的

unique_ptrauto_ptr 用法几乎一样,除了一些特殊,见下:

a. 无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unique_ptr<string> p1(new string("I'm Li Ming!"));
unique_ptr<string> p2(new string("I'm age 22."));

cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;

p1 = p2; // 禁止左值赋值
unique_ptr<string> p3(p2); // 禁止左值赋值构造

unique_ptr<string> p3(std::move(p1));
p1 = std::move(p2); // 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样

cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;

/* output
p1:0xed1dd0
p2:0xed1e00
p1 = p2 赋值后:
p1:0xed1e00
p2:0
*/

b. 在 STL 容器中使用unique_ptr,不允许直接赋值 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<unique_ptr<string>> vec;
unique_ptr<string> p3(new string("I'm P3"));
unique_ptr<string> p4(new string("I'm P4"));

vec.push_back(std::move(p3));
vec.push_back(std::move(p4));

cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

vec[0] = vec[1]; /* 不允许直接赋值 */
vec[0] = std::move(vec[1]); // 需要使用move修饰,使得程序员知道后果

cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

/* output
vec.at(0):I'm P3
vec[1]:I'm P4
vec.at(0):I'm P4
vec[1]:
*/

c. 支持对象数组的内存管理:

1
2
// 会自动调用delete [] 函数去释放内存
unique_ptr<int[]> array(new int[5]); // 支持这样定义

3.2 使用实例

构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Test {
public:
Test() { cout << "Test的构造函数..." << endl; }
~Test() { cout << "Test的析构函数..." << endl; }

void doSomething() { cout << "do something......" << endl; }
};


// 自定义一个内存释放其
class DestructTest {
public:
void operator()(Test *pt) {
pt->doSomething();
delete pt;
}
};

// unique_ptr<T> up; 空的unique_ptr,可以指向类型为T的对象
unique_ptr<Test> t1;

// unique_ptr<T> up1(new T()); 定义unique_ptr,同时指向类型为T的对象
unique_ptr<Test> t2(new Test);

// unique_ptr<T[]> up; 空的unique_ptr,可以指向类型为T[的数组对象
unique_ptr<int[]> t3;

// unique_ptr<T[]> up1(new T[]); 定义unique_ptr,同时指向类型为T的数组对象
unique_ptr<int[]> t4(new int[5]);

// unique_ptr<T, D> up(); 空的unique_ptr,接受一个D类型的删除器D,使用D释放内存
unique_ptr<Test, DestructTest> t5;

// unique_ptr<T, D> up(new T()); 定义unique_ptr,同时指向类型为T的对象,接受一个D类型的删除器D,使用删除器D来释放内存
unique_ptr<Test, DestructTest> t6(new Test);

赋值:

1
2
3
4
unique_ptr<Test> t7(new Test);
unique_ptr<Test> t8(new Test);
t7 = std::move(t8); // 必须使用移动语义,结果,t7的内存释放,t8的内存交给t7管理
t7->doSomething();

主动释放对象:

1
2
3
4
unique_ptr<Test> t9(new Test);
t9 = NULL;
t9 = nullptr;
t9.reset();

放弃对象的控制权:

1
Test *t10 = t9.release();

重置:

1
t9.reset(new Test);

3.3 缺陷

1
2
3
4
5
6
7
8
9
10
auto_ptr<string> p1;
string *str = new string("智能指针的内存管理陷阱");
p1.reset(str); // p1托管str指针
{
auto_ptr<string> p2;
p2.reset(str); // p2接管str指针时,会先取消p1的托管,然后再对str的托管
}

// 此时p1已经没有托管内容指针了,为NULL,在使用它就会内存报错!
cout << "str:" << *p1 << endl;

这是由于 auto_ptrunique_ptr 的排他性所导致的!

为了解决这样的问题,我们可以使用 shared_ptr 指针!

四、shared_ptr

如果有一种方式,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
public:
Person(int v) {
this->no = v;
cout << "构造函数 \t no = " << this->no << endl;
}

~Person() {
cout << "析构函数 \t no = " << this->no << endl;
}

private:
int no;
};

// 仿函数,内存删除
class DestructPerson {
public:
void operator() (Person *pt) {
cout << "DestructPerson..." << endl;
delete pt;
}
};

4.1 使用实例

  1. 调用 use_count 函数可以获得当前托管指针的引用计数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    shared_ptr<Person> sp1;

    shared_ptr<Person> sp2(new Person(2));

    // 获取智能指针管控的共享指针的数量 use_count():引用计数
    cout << "sp1 use_count() = " << sp1.use_count() << endl;
    cout << "sp2 use_count() = " << sp2.use_count() << endl << endl;

    // 共享
    sp1 = sp2;

    cout << "sp1 use_count() = " << sp1.use_count() << endl;
    cout << "sp2 use_count() = " << sp2.use_count() << endl << endl;

    shared_ptr<Person> sp3(sp1);
    cout << "sp1 use_count() = " << sp1.use_count() << endl;
    cout << "sp2 use_count() = " << sp2.use_count() << endl;
    cout << "sp2 use_count() = " << sp3.use_count() << endl << endl;

    /* output
    构造函数 no = 2
    sp1 use_count() = 0
    sp2 use_count() = 1

    sp1 use_count() = 2
    sp2 use_count() = 2

    sp1 use_count() = 3
    sp2 use_count() = 3
    sp2 use_count() = 3

    析构函数 no = 2
    */
  2. 构造

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    shared_ptr<Person> sp1;
    Person *person1 = new Person(1);
    sp1.reset(person1); // 托管person1

    shared_ptr<Person> sp2(new Person(2));
    shared_ptr<Person> sp3(sp1);

    // C++17后支持
    shared_ptr<Person[]> sp4;

    // C++17后支持
    shared_ptr<Person[]> sp5(new Person[5] { 3, 4, 5, 6, 7 });

    shared_ptr<Person> sp6(NULL, DestructPerson());

    shared_ptr<Person> sp7(new Person(8), DestructPerson());

  3. 初始化

    1
    2
    3
    4
    5
    6
    7
    8
    // 方式一:构造函数
    shared_ptr<int> up1(new int(10)); // int(10) 的引用计数为1
    shared_ptr<int> up2(up1); // 使用智能指针up1构造up2, 此时int(10) 引用计数为2

    // 方式二:使用make_shared初始化对象,分配内存效率更高
    shared_ptr<int> up3 = make_shared<int>(2); // 多个参数以逗号','隔开,最多接受十个
    shared_ptr<string> up4 = make_shared<string>("字符串");
    shared_ptr<Person> up5 = make_shared<Person>(9);
  4. 赋值

    1
    2
    3
    shared_ptrr<int> up1(new int(10));  // int(10) 的引用计数为1
    shared_ptr<int> up2(new int(11)); // int(11) 的引用计数为1
    up1 = up2; // int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)的引用计数为2
  5. 主动释放对象

    1
    2
    3
    4
    shared_ptrr<int> up1(new int(10));
    up1 = nullptr ; // int(10) 的引用计数减1,计数归零内存释放
    // 或
    up1 = NULL; // 作用同上
  6. 重置

    1
    2
    3
    4
    p.reset() ; 将p重置为空指针,所管理对象引用计数 减1
    p.reset(p1); 将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控
    p.reset(p1,d); 将p重置为p1(的值),p 管控的对象计数减1并使用d作为删除器
    // p1是一个指针!
  7. 交换

    1
    2
    3
    // p1 和 p2 是智能指针
    std::swap(p1,p2); // 交换p1 和p2 管理的对象,原对象的引用计数不变
    p1.swap(p2); // 交换p1 和p2 管理的对象,原对象的引用计数不变

4.2 缺陷

shared_ptr 作为被管控的对象的成员时,小心因循环引用造成无法释放资源!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <string>
#include <memory>

using namespace std;

class Girl;

class Boy {
public:
Boy() {
cout << "Boy 构造函数" << endl;
}

~Boy() {
cout << "~Boy 析构函数" << endl;
}

void setGirlFriend(shared_ptr<Girl> _girlFriend) {
this->girlFriend = _girlFriend;
}

private:
shared_ptr<Girl> girlFriend;
};

class Girl {
public:
Girl() {
cout << "Girl 构造函数" << endl;
}

~Girl() {
cout << "~Girl 析构函数" << endl;
}

void setBoyFriend(shared_ptr<Boy> _boyFriend) {
this->boyFriend = _boyFriend;
}

private:
shared_ptr<Boy> boyFriend;
};


void useTrap() {
shared_ptr<Boy> spBoy(new Boy());
shared_ptr<Girl> spGirl(new Girl());

// 陷阱用法
spBoy->setGirlFriend(spGirl);
spGirl->setBoyFriend(spBoy);
// 此时boy和girl的引用计数都是2
}


int main(void) {
useTrap();

system("pause");
return 0;
}

/* output
Boy 构造函数
Girl 构造函数
*/

可以看出,程序结束了,但是并没有释放内存 。

当执行 useTrap 函数时,注意,是没有结束此函数,boy 和 girl 指针其实是被两个智能指针托管的,所以他们的引用计数是 2。

useTrap 函数结束后,函数中定义的智能指针被清掉,boy 和 girl 指针的引用计数减 1,还剩下 1,对象中的智能指针还是托管他们的,所以函数结束后没有将 boy 和 girl 指针释放的原因就是于此。

所以在使用 shared_ptr智能指针时,要注意避免对象交叉使用智能指针的情况! 否则会导致内存泄露!

当然,这也是有办法解决的,那就是使用 weak_ptr 指针。

五、weak_ptr

weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时 weak_ptr 没有重载 *-> 但可以使用 lock 获得一个可用的 shared_ptr 对象。

5.1 使用实例

  1. 弱指针的使用;

    1
    2
    3
    weak_ptr wpGirl_1; // 定义空的弱指针
    weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
    wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针
  2. 弱指针也可以获得引用计数;

    1
    wpGirl_1.use_count()
  3. 弱指针不支持 * 和 -> 对指针的访问;

  4. 在必要的使用可以转换成共享指针 lock();

1
2
3
4
5
shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();

// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <iostream>
#include <string>
#include <memory>

using namespace std;

class Girl;

class Boy {
public:
Boy() {
cout << "Boy 构造函数" << endl;
}

~Boy() {
cout << "~Boy 析构函数" << endl;
}

void setGirlFriend(shared_ptr<Girl> _girlFriend) {
this->girlFriend = _girlFriend;


// 在必要的使用可以转换成共享指针
shared_ptr<Girl> sp_girl;
sp_girl = this->girlFriend.lock();

cout << sp_girl.use_count() << endl;
// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;
}

private:
weak_ptr<Girl> girlFriend;
};

class Girl {
public:
Girl() {
cout << "Girl 构造函数" << endl;
}

~Girl() {
cout << "~Girl 析构函数" << endl;
}

void setBoyFriend(shared_ptr<Boy> _boyFriend) {
this->boyFriend = _boyFriend;
}

private:
shared_ptr<Boy> boyFriend;
};


void useTrap() {
shared_ptr<Boy> spBoy(new Boy());
shared_ptr<Girl> spGirl(new Girl());

spBoy->setGirlFriend(spGirl);
spGirl->setBoyFriend(spBoy);
}


int main(void) {
useTrap();

system("pause");
return 0;
}
/* output
Boy 构造函数
Girl 构造函数
3
~Girl 析构函数
~Boy 析构函数
*/

C++智能指针
http://example.com/2026/05/13/C++智能指针/
作者
Yu xin
发布于
2026年5月13日
许可协议