文章目录
前言
在C++11中通过引入智能指针的概念,使得C++程序员不需要手动释放内存。
一、智能指针的种类
- std::unique_ptr
- std::shared_ptr
- std::weak_ptr
注意:std::auto_ptr已被废弃
二、智能指针的概述
C++的指针包括两种
- 原始指针(raw pointer)
- 智能指针
智能指针是对原始指针的封装,其优点是自动分配内存,不用担心潜在的内存泄漏。并不是所有的指针都可以封装成智能指针,很多时候原始指针要更方便。
各种指针中,最常用的是裸指针(原始指针),其次是unique_ptr和shared_ptr,weak_ptr是shared_ptr的一个补充,应用场景较少。
智能指针与Rust的内存安全
智能指针只解决一部分问题,即独占/共享所有权指针的释放、传输。但智能指针没有从根本上解决C++内存安全问题,不加以注意依然会造成内存安全问题。
三、独占指针:unique_ptr
unique_ptr在任何给定的时刻,只能有一个指针管理内存,当指针超出作用域时,内存将自动释放,该类型指针不可Copy,只可以Move。
三种创建方式:
- 通过已有裸指针创建
- 通过new来创建
- 通过std::make_unique创建(推荐)
unique_ptr可以通过get()获取地址
unique_ptr实现了->与*
- 可以通过->调用成员函数
- 可以通过*调用dereferencing
cat.h
#ifndef POINTER_CAT_H
#define POINTER_CAT_H
#include <string>
#include <iostream>
class Cat
{
public:
Cat(std::string name);
Cat() = default;
~Cat();
// ->
void cat_info() const
{
std::cout << "cat info name: " << name << std::endl;
}
std::string get_name()
{
return name;
}
void set_name(const std::string &name)
{
this->name = name;
}
private:
std::string name;
};
#endif //POINTER_CAT_H
cat.cpp
#include "cat.h"
Cat::Cat(std::string name):name(name)
{
std::cout << "Construct of Cat: " << name << std::endl;
}
Cat::~Cat()
{
std::cout << "Destructor of Cat: " << name << std::endl;
}
不加指针 main.cpp
#include "cat.h"
#include <memory>
using namespace std;
int main(int argc, char *argv[])
{
// stack
Cat c("OK");
c.cat_info();
{
Cat c("OK");
c.cat_info();
}
cout << "ending......" << endl;
return 0;
}
输出
Construct of Cat: OK
cat info name: OK
Construct of Cat: OK
cat info name: OK
Destructor of Cat: OK //局部作用域结束就会自动析构
ending......
Destructor of Cat: OK
原始指针main.cpp
#include "cat.h"
#include <memory>
using namespace std;
int main(int argc, char *argv[])
{
Cat *c_p1 = new Cat("kiki");
c_p1->cat_info();
{
Cat *c_p1 = new Cat("gigi");
c_p1->cat_info();
}
cout << "ending......" << endl;
return 0;
}
Construct of Cat: kiki
cat info name: kiki
Construct of Cat: gigi
cat info name: gigi
ending......
堆上的空间不会自动释放,需要手动调用delete
#include "cat.h"
#include <memory>
using namespace std;
int main(int argc, char *argv[])
{
Cat *c_p1 = new Cat("kiki");
c_p1->cat_info();
{
Cat *c_p1 = new Cat("gigi");
c_p1->cat_info();
delete c_p1;
}
delete c_p1;
cout << "ending......" << endl;
return 0;
}
Construct of Cat: kiki
cat info name: kiki
Construct of Cat: gigi
cat info name: gigi
Destructor of Cat: gigi
Destructor of Cat: kiki
ending......
上面是自定义类型,下面换成系统提供的类型
int main(int argc, char *argv[])
{
int *i = new int(100);
{
int *i = new int(200);
}
cout << *i << endl;
cout << "ending......" << endl;
return 0;
}
100
ending......
修改一下
int main(int argc, char *argv[])
{
int *i = new int(100);
{
i = new int(200);
}
cout << *i << endl;
cout << "ending......" << endl;
return 0;
}
200
ending......
再修改一下
int main(int argc, char *argv[])
{
int *i = new int(100);
{
i = new int(200);
delete i;
}
cout << *i << endl;
delete i;
cout << "ending......" << endl;
return 0;
}
pointer(1070,0x104bdc580) malloc: *** error for object 0x600003f20040: pointer being freed was not allocated
程序报错,上述代码存在的问题是第二次delete,并且第一次开辟的空间100没有释放,造成内存泄漏。也就是说,在使用原始指针时,每次new都需要delete,忘记delete或者额外的delete都可能造成程序错误,所以是不安全不方便的。
1、unique_ptr三种创建方式
1、通过已有裸指针创建
Cat *c_p = new Cat("kiki");
std::unique_ptr<Cat> u_c_p{c_p};
c_p->cat_info(); // cat info name: kiki
u_c_p->cat_info(); // cat info name: kiki
// 当使用原始指针改变对象时,独占指针也会随之改变
// 这样就不满足"独占"的含义
c_p->set_name("haha");
u_c_p->cat_info(); // cat info name: haha
// 建议销毁原始指针
c_p = nullptr;
delete c_p;
u_c_p->cat_info(); // cat info name: haha
cout << "ending......" << endl;
2、通过new创建
std::unique_ptr<Cat> u_ptr{new Cat("dd")};
u_ptr->cat_info();
u_ptr->set_name("haha");
u_ptr->cat_info();
cout << "ending......" << endl;
3、通过std::make_unique创建(推荐)
这种方式只有c++14才支持
std::unique_ptr<Cat> u_ptr = make_unqiue<Cat>();
// std::unique_ptr<int> u_i = make_unique<int>(100);
// cout << u_i.get() << endl;
u_ptr->cat_info();
u_ptr->set_name("haha");
u_ptr->cat_info();
以上三种方式都会自动调用析构函数
四、unique_ptr与函数调用
unique_ptr是不可copy,只可以move,在做函数参数或者返回值中一定要注意所有权。
1、函数调用与unique_ptr注意事项
- Passing by value
需要使用std::move来转移内存拥有权
如果参数直接传入std::make_unique语句自动转换为move - Passing by reference
如果设置参数为const则不能改变指向(比如reset()方法,reset()方法为智能指针清空方法) - Return by value
指向一个local object,可以用作链式函数
1、passing by value
void do_with_cat_pass_value(std::unique_ptr<Cat> c)
{
c->cat_info();
}
std::unique_ptr<Cat> c{new Cat("kiki")};
do_with_cat_pass_value(std::move(c));
// do_with_cat_pass_value(std::make_unique<Cat>()); // 隐式转换
// c->cat_info(); /// 已经move 调用报错
cout << "ending......" << endl;
return 0;
2、passing by ref
不加const
void do_with_cat_pass_ref(std::unique_ptr<Cat> &c)
{
c->set_name("haha");
c->cat_info();
c.reset();
}
std::unique_ptr<Cat> u_p{new Cat("kiki")};
// 不加const
do_with_cat_pass_ref(u_p);
cout << "address:" << u_p.get() << endl; // 0x0
cout << "ending......" << endl;
return 0;
加const
void do_with_cat_pass_ref(const std::unique_ptr<Cat> &c)
{
c->set_name("haha"); // 居然还可以修改
c->cat_info();
// c.reset();
}
std::unique_ptr<Cat> u_p{new Cat("kiki")};
// 不加const
do_with_cat_pass_ref(u_p);
cout << "address:" << u_p.get() << endl;
cout << "ending......" << endl;
return 0;
3、return by value
std::unique_ptr<Cat> get_unique_ptr()
{
std::unique_ptr<Cat> u_p{new Cat("local cat")};
cout << u_p.get() << endl;
cout << &u_p << endl;
// get返回的是指针指向的内存地址
// &返回的是指针的地址
return u_p;
}
get_unique_ptr()->cat_info();
cout << "ending......" << endl;
return 0;
五、计数指针:shared_ptr
shared_ptr计数指针又称共享指针,与unique_ptr不同的是它是可以共享数据的。shared_ptr创建了一个计数器与泪对象所指的内存相关联,copy则计数器加一,销毁则计数器减一,查看计数的api为use_count().
常量类型
std::shared_ptr<int> s_p1 = make_shared<int>(10);
cout << "value:" << *s_p1 << endl; // 10
cout << "use count:" << s_p1.use_count() << endl; // 1
std::shared_ptr<int> s_p2 = s_p1;
cout << "s_p1 use count:" << s_p1.use_count() << endl; // 2
cout << "s_p2 use count:" << s_p2.use_count() << endl; // 2
*s_p2 = 30;
cout << "value:" << *s_p1 << endl; // 30
cout << "s_p1 use count:" << s_p1.use_count() << endl; // 2
cout << "s_p2 use count:" << s_p2.use_count() << endl; // 2
s_p2 = nullptr;
cout << "s_p1 use count:" << s_p1.use_count() << endl; // 1
cout << "s_p2 use count:" << s_p2.use_count() << endl; // 0
return 0;
自定义数据类型
std::shared_ptr<Cat> c_p1 = make_shared<Cat>();
cout << "c_p1 use_count:" << c_p1.use_count() << endl;
std::shared_ptr<Cat> c_p2 = c_p1;
std::shared_ptr<Cat> c_p3 = c_p2;
cout << "c_p1 use_count:" << c_p1.use_count() << endl;
cout << "c_p2 use_count:" << c_p2.use_count() << endl;
cout << "c_p3 use_count:" << c_p3.use_count() << endl;
c_p1.reset();
cout << "c_p1 use_count:" << c_p1.use_count() << endl;
cout << "c_p2 use_count:" << c_p2.use_count() << endl;
cout << "c_p3 use_count:" << c_p3.use_count() << endl;
return 0;
注意:不管计数是多少,数据在内存中只有一份,一次构造,一次销毁。
六、shared_ptr与函数
-
shared_ptr passed by value
copy
函数内部计数器加一void cat_by_value(std::shared_ptr<Cat> cat) { cout << cat->get_name() << endl; cat->set_name("haha"); cout << "func use count:" << cat.use_count() << endl; // 2 }
std::shared_ptr<Cat> c1 = make_shared<Cat>("kiki"); cat_by_value(c1); c1->cat_info(); // haha cout << "use count" << c1.use_count() << endl; //1
-
shared_ptr passed by ref
const表示不可改变指向void cat_by_value(std::shared_ptr<Cat> cat) { cout << cat->get_name() << endl; cat->set_name("haha"); cout << "func use count:" << cat.use_count() << endl; // 2 } void cat_by_ref(std::shared_ptr<Cat> &cat) { cout << cat->get_name() << endl; cat.reset(new Cat("dd")); // 加了const就不能调用 cout << "func use count:" << cat.use_count() << endl; }
std::shared_ptr<Cat> c1 = make_shared<Cat>("kiki"); cat_by_value(c1); c1->cat_info(); // haha cout << "use count" << c1.use_count() << endl; //1 cat_by_ref(c1); c1->cat_info(); return 0;
Construct of Cat: kiki kiki func use count:2 cat info name: haha use count1 haha Construct of Cat: dd Destructor of Cat: haha func use count:1 cat info name: dd Destructor of Cat: dd
-
returning by value
链式调用std::shared_ptr<Cat> get_shared_ptr() { std::shared_ptr<Cat> cat_ptr = make_shared<Cat>("local cat"); return cat_ptr; }
七、shared_ptr与unique_ptr的转化
不能将shared_ptr转换为unique_ptr,unique_ptr可以转换为shared_ptr(通过std::move)
常见的设计:将你的函数返回unique_ptr是一种常见的设计模式,这样可以提高代码的复用度,你可以随时改变为shared_ptr
std::unique_ptr<Cat> c_p1 = unique_ptr<Cat>{new Cat("kiki")};
std::shared_ptr<Cat> c_p2 = std::move(c_p1);
cout << c_p2.use_count() << endl; //1
std::shared_ptr<Cat> c_p3 = get_unique_ptr(); // 隐式转换
if(c_p3){
cout << c_p3.use_count() << endl; //1
}
八、weak_ptr:shared_ptr的补充
weak_ptr并不拥有内存的所有权,并不能调用->和解引用*。
为什么需要weak_ptr呢?
假设A类中有一个需求需要存储其他A类对象的信息,如果使用shared_ptr,那么在销毁时会遇到循环依赖问题(Cyclic dependency problem),所以这里需要一个不需要拥有所有权的指针来标记该同类对象(weak_ptr可以通过lock()函数来提升为shared_ptr(类型转换))
cat.h
#ifndef POINTER_CAT_H
#define POINTER_CAT_H
#include <string>
#include <iostream>
#include <memory>
class Cat
{
public:
Cat(std::string name);
Cat() = default;
~Cat();
// ->
void cat_info() const
{
std::cout << "cat info name: " << name << std::endl;
}
std::string get_name()
{
return name;
}
void set_name(const std::string &name)
{
this->name = name;
}
void set_friend(std::shared_ptr<Cat> c)
{
m_friend = c;
}
private:
std::string name;
std::shared_ptr<Cat> m_friend;
};
#endif //POINTER_CAT_H
std::shared_ptr<Cat> s_p1 = std::make_shared<Cat>("kiki");
std::weak_ptr<Cat> w_p1(s_p1);
cout << "s_p1 use count:" << s_p1.use_count() << endl; // 1
// weak_ptr可以调用use_count,但计数并不会加1
cout << "w_p1 use count:" << w_p1.use_count() << endl; // 1
// w_p1.cat_info() 报错
std::shared_ptr<Cat> s_p2 = w_p1.lock();
cout << "s_p1 use count:" << s_p1.use_count() << endl; // 2
cout << "s_p1 use count:" << s_p2.use_count() << endl; // 2
cout << "w_p1 use count:" << w_p1.use_count() << endl; // 2
cout << "##############循环依赖##############" << endl;
// 循环依赖
std::shared_ptr<Cat> cat1 = std::make_shared<Cat>("cat1");
std::shared_ptr<Cat> cat2 = std::make_shared<Cat>("cat2");
return 0;
Construct of Cat: kiki
s_p1 use count:1
w_p1 use count:1
s_p1 use count:2
s_p1 use count:2
w_p1 use count:2
##############循环依赖##############
Construct of Cat: cat1
Construct of Cat: cat2
Destructor of Cat: cat2
Destructor of Cat: cat1
Destructor of Cat: kiki
目前上述代码没有任何问题
std::shared_ptr<Cat> cat1 = std::make_shared<Cat>("cat1");
std::shared_ptr<Cat> cat2 = std::make_shared<Cat>("cat2");
// 将cat1的朋友设置为cat2
// 将cat2的朋友设置为cat1
cat1->set_friend(cat2);
cat2->set_friend(cat1);
Construct of Cat: kiki
s_p1 use count:1
w_p1 use count:1
s_p1 use count:2
s_p1 use count:2
w_p1 use count:2
##############循环依赖##############
Construct of Cat: cat1
Construct of Cat: cat2
Destructor of Cat: kiki
此时,cat1和cat2由于循环依赖并没有正常销毁,而程序也正常退出了。
解决这个问题,只需要将cat.h中的std::shared_ptr改为weak_ptr即可。
Construct of Cat: kiki
s_p1 use count:1
w_p1 use count:1
s_p1 use count:2
s_p1 use count:2
w_p1 use count:2
##############循环依赖##############
Construct of Cat: cat1
Construct of Cat: cat2
Destructor of Cat: cat2
Destructor of Cat: cat1
Destructor of Cat: kiki
修改后,都正常销毁了!