1、共享指针shared_ptr
- shared_ptr共享指针又称为计数指针,与unique_ptr不同的是它可以共享数据
- shared_ptr创建了一个计数器与来对象所致的内存相关联
- Copy则计数器加1,销毁则计数器减1,Move移动计数器不加不减
- 可以通过use_count来查看当前有多少个计数指针。
1.1、计数问题
void test_shared_ptr1()
{
std::shared_ptr<int> p1 = std::make_shared<int>(123);
std::shared_ptr<int> p2 = p1;
std::cout << "p1->val = " << *p1 << ", p1.use_count = " << p1.use_count() << std::endl;
std::cout << "p2->val = " << *p2 << ", p2.use_count = " << p2.use_count() << std::endl;
*p2 = 456;
std::cout << "*p2 = 456;" << std::endl;
std::cout << "p1->val = " << *p1 << ", p1.use_count = " << p1.use_count() << std::endl;
std::cout << "p2->val = " << *p2 << ", p2.use_count = " << p2.use_count() << std::endl;
auto p3 = p2;
std::cout << "auto p3 = p2;" << std::endl;
std::cout << "p1->val = " << *p1 << ", p1.use_count = " << p1.use_count() << std::endl;
std::cout << "p2->val = " << *p2 << ", p2.use_count = " << p2.use_count() << std::endl;
std::cout << "p3->val = " << *p3 << ", p2.use_count = " << p3.use_count() << std::endl;
auto p4= std::move(p3); //移交所有权,不会进行拷贝或赋值重载
std::cout << "auto p4= std::move(p3);" << std::endl;
std::cout << "p1->val = " << *p1 << ", p1.use_count = " << p1.use_count() << std::endl;
std::cout << "p2->val = " << *p2 << ", p2.use_count = " << p2.use_count() << std::endl;
// std::cout << "p3->val = " << *p3 << ", p2.use_count = " << p3.use_count() << std::endl; // 报错
std::cout << "p4->val = " << *p4 << ", p4.use_count = " << p4.use_count() << std::endl;
}
/* 输出
p1->val = 123, p1.use_count = 2
p2->val = 123, p2.use_count = 2
*p2 = 456;
p1->val = 456, p1.use_count = 2
p2->val = 456, p2.use_count = 2
auto p3 = p2;
p1->val = 456, p1.use_count = 3
p2->val = 456, p2.use_count = 3
p3->val = 456, p2.use_count = 3
auto p4= std::move(p3);
p1->val = 456, p1.use_count = 3
p2->val = 456, p2.use_count = 3
p4->val = 456, p4.use_count = 3
*/
1.2、内存共享与销毁问题
- shared_ptr指针所有对象只会创建一次,并且只会初始化一次,多次计数时只是多个指针指向了这一块地址
- 析构时也只会析构一次,将这一块内存地址进行释放
- 而reset()方法和指针修改为nullptr都只会把指向修改,并不会释放空间
void test_shared_ptr2()
{
std::shared_ptr<Cat> p1 = std::make_shared<Cat>("小猫");
std::shared_ptr<Cat> p2 = p1;
std::shared_ptr<Cat> p3 = p1;
p1.reset();
std::cout << "p1.use_count = " << p1.use_count() << std::endl;
std::cout << "p2.use_count = " << p2.use_count() << std::endl;
std::cout << "p3.use_count = " << p3.use_count() << std::endl;
p2 = nullptr;
std::cout << "p1.use_count = " << p1.use_count() << std::endl;
std::cout << "p2.use_count = " << p2.use_count() << std::endl;
std::cout << "p3.use_count = " << p3.use_count() << std::endl;
std::cout << __func__ << std::endl;
}
/* 输出
Constructor of Cat: 小猫
p1.use_count = 0
p2.use_count = 2
p3.use_count = 2
p1.use_count = 0
p2.use_count = 0
p3.use_count = 1
test_shared_ptr2 // 打印输出的函数名字
Destructor of Cat: 小猫
*/
1.3、值传递
- 既然shared_ptr可以进行拷贝和赋值重载,那么就可以当值进行传入了
void pass_value(std::shared_ptr<Cat> ptr)
{
std::cout << "pass_value():: ptr.use_count = " << ptr.use_count() << std::endl;
ptr->set_Name("Mimi");
}
void test_shared_ptr3()
{
std::shared_ptr<Cat> ptr = std::make_shared<Cat>("小猫");
std::cout << "test_shared_ptr3():: ptr.use_count = " << ptr.use_count() << std::endl;
pass_value(ptr);
ptr->catInfo();
std::cout << "test_shared_ptr3():: ptr.use_count = " << ptr.use_count() << std::endl;
}
/*
Constructor of Cat: 小猫
test_shared_ptr3():: ptr.use_count = 1
pass_value():: ptr.use_count = 2
cat info name: Mimi
test_shared_ptr3():: ptr.use_count = 1
Destructor of Cat: Mimi
*/
1.3、引用传递
- 传递引用意义不是很大,而且容易出问题,需要防止提前空间释放的问题
void pass_ref(std::shared_ptr<Cat>& ptr)
{
std::cout << "pass_value():: ptr.use_count = " << ptr.use_count() << std::endl;
ptr->set_Name("Mimi");
ptr.reset();
}
void test_shared_ptr4()
{
std::shared_ptr<Cat> ptr = std::make_shared<Cat>("小猫");
std::cout << "test_shared_ptr4():: ptr.use_count = " << ptr.use_count() << std::endl;
pass_ref(std::ref(ptr));
// ptr->catInfo(); //报错
std::cout << "test_shared_ptr4():: ptr.use_count = " << ptr.use_count() << std::endl;
}
/*
Constructor of Cat: 小猫
test_shared_ptr4():: ptr.use_count = 1
pass_value():: ptr.use_count = 1
Destructor of Cat: Mimi
test_shared_ptr4():: ptr.use_count = 0
*/
2、unique_ptr转换为shared_ptr
-
unique_ptr可以转化为shared_ptr,shared_ptr无法转化为unique_ptr。
-
一个函数返回一个unique_ptr的智能指针有一个很强的兼容性,可以用unique_ptr接受也可以用shared_ptr接受
-
其内部在返回时会将当前智能指针进行移动构造,也就是std::move的完全移交
std::unique_ptr<Cat> get_unique_ptr()
{
std::unique_ptr<Cat> ptr = std::make_unique<Cat>("老猫");
std::cout << "cat address = " << ptr.get() << std::endl;
ptr->set_Name("Mimi");
return ptr;
}
void test_shared_ptr5()
{
std::shared_ptr<Cat> ptr = get_unique_ptr();
std::cout << "cat address = " << ptr.get() << std::endl;
}
/*
Constructor of Cat: 老猫
cat address = 0x55d5fe193e70
cat address = 0x55d5fe193e70
Destructor of Cat: Mimi
*/
3、weak_ptr
- weak_ptr:弱指针,它并不拥有指针的所有权,因此不能调用->和解引用*
- weak_ptr无法单独创建实例的指针,一般是伴随着shared_ptr存在
3.1、弱指针的计数
void weak_ptr1()
{
std::shared_ptr<Cat> sp = std::make_shared<Cat>("小猫");
std::weak_ptr<Cat> wp(sp);
std::cout << "sp.use_count = " << sp.use_count() << std::endl;
std::cout << "wp.use_count = " << wp.use_count() << std::endl;
sp.reset();
std::cout << "sp.use_count = " << sp.use_count() << std::endl;
std::cout << "wp.use_count = " << wp.use_count() << std::endl;
}
/* 输出
Constructor of Cat: 小猫
sp.use_count = 1
wp.use_count = 1
Destructor of Cat: 小猫
sp.use_count = 0
wp.use_count = 0
*/
void weak_ptr2()
{
std::shared_ptr<Cat> sp = std::make_shared<Cat>("小猫");
std::weak_ptr<Cat> wp(sp);
std::cout << "sp.use_count = " << sp.use_count() << std::endl;
std::cout << "wp.use_count = " << wp.use_count() << std::endl;
wp.reset();
std::cout << "sp.use_count = " << sp.use_count() << std::endl;
std::cout << "wp.use_count = " << wp.use_count() << std::endl;
}
/* 输出
Constructor of Cat: 小猫
sp.use_count = 1
wp.use_count = 1
sp.use_count = 1
wp.use_count = 0
Destructor of Cat: 小猫
*/
3.2、函数调用与升级
- 由于weak_ptr并不拥有指针的所有权,因此无法调用指针指向对象的函数
void weak_ptr3()
{
std::shared_ptr<Cat> sp = std::make_shared<Cat>("小猫");
std::weak_ptr<Cat> wp(sp);
sp->catInfo();
// wp->catInfo(); // 报错 因为没有重载->符号
std::shared_ptr<Cat> sp2 = wp.lock(); // 需要调用时要调用lock进行升级成shared_ptr类型
sp2->catInfo();
std::cout << "sp.use_count = " << sp.use_count() << std::endl;
std::cout << "wp.use_count = " << wp.use_count() << std::endl;
std::cout << "sp2.use_count = " << sp2.use_count() << std::endl;
}
/*
Constructor of Cat: 小猫
cat info name: 小猫
cat info name: 小猫
sp.use_count = 2
wp.use_count = 2
sp2.use_count = 2
Destructor of Cat: 小猫
*/
3.3、循环依赖问题
- 循环依赖:面向对象程序中非常常见的一个问题,类A依赖类B,类B依赖类A或者多个类形成环形就叫循环依赖
- 如果使用shared_ptr在销毁时就会遇到循环依赖问题,Java语言在构建对象时会有循环依赖的问题(Spring框架用三级缓存解决)
class Teacher;
class School{
public:
std::string name;
int age;
std::shared_ptr<Teacher> headTeacher;
virtual ~School(){
std::cout << "School Destructed!" << std::endl;
}
};
class Teacher{
public:
std::string name;
std::shared_ptr<School> school;
virtual ~Teacher(){
std::cout << "Teacher Destructed!" << std::endl;
}
};
void weak_ptr4()
{
std::shared_ptr<School> university = std::make_shared<School>();
std::shared_ptr<Teacher> teacher = std::make_shared<Teacher>();
university->headTeacher = teacher;
teacher->school = university;
std::cout << __func__ << std::endl;
}
// 只输出:weak_ptr4
-
很明显这里会发现university和teacher相互依赖,导致shared_ptr的计数引用无法降为0形成了死锁。
-
在释放对象时二者都不肯先一步析构,最后无法析构导致内存泄漏
-
解决方案:只需要将其中一个的shared_ptr弱化成weak_ptr指针即可。