C++11 引入了智能指针,以更好地管理动态分配的资源,主要包括
-
std::shared_ptr:共享的智能指针
-
std::unique_ptr:独占的智能指针
-
std::weak_ptr:弱引用的智能指针
智能指针通过 RAII(Resource Acquisition Is Initialization)原则,在超出作用域时自动释放资源,从而减少内存泄漏和悬挂指针的风险。智能指针的核心技术是引用计数,每次使用它一次内部引用计数加1,每析构一次内部引用计数减1,当为0时,释放原始指针指向的堆区内存。
RAII(Resource Acquisition Is Initialization)原则是一种用于管理资源(如内存、文件句柄、网络连接等)的编程技术。它的核心思想是将资源的获取和释放与对象的生命周期绑定在一起。具体来说,当对象被创建时,它会自动获取所需的资源;当对象被销毁时,它会自动释放这些资源。
shared_ptr(共享智能指针)
初始化
共享智能指针是指多个智能指针可以同时管理同一块有效内存,共享智能指针是一个模板类,如果进行初始化有三种方式:
-
通过构造函数初始化
-
std::make_share 辅助函数
-
reset方法
可以使用成员函数use_count来查看当前有多少个智能指针同时管理着这块内存
构造函数初始化:
#include<iostream>
#include<memory>
using namespace std;
int main() {
shared_ptr<int> ptr1(new int(520)); //构造函数初始化
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
shared_ptr<int>ptr2 = ptr1;//通过拷贝构造使得两个智能指针指向同一块内存
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
shared_ptr<int>ptr3 = move(ptr2);//移动构造
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
shared_ptr<int>ptr4;//默认构造
cout << "ptr4的引用计数:" << ptr4.use_count() << endl;
shared_ptr<int>ptr5(NULL);
cout << "ptr5的引用计数:" << ptr5.use_count() << endl;
}
/*
结果:
ptr1的引用计数:1
ptr2的引用计数:2
ptr3的引用计数:2
ptr4的引用计数:0
ptr5的引用计数:0
*/
注意:不要使用同一个原始指针去初始化多个智能指针,会造成同一块内存重复释放
引用计数的原理需要用智能指针去创建另一个智能指针才会计数+1。
#include<iostream>
#include<memory>
using namespace std;
int main() {
shared_ptr<int> ptr1(new int(520));
//拷贝构造
shared_ptr<int>ptr2 = ptr1;
shared_ptr<int>ptr3 = ptr1;
//移动构造
shared_ptr<int>ptr4(move(ptr1));
shared_ptr<int>ptr5 = move(ptr2);
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
cout << "ptr4的引用计数:" << ptr4.use_count() << endl;
cout << "ptr5的引用计数:" << ptr5.use_count() << endl;
}
/*
结果:
ptr1的引用计数:0
ptr2的引用计数:0
ptr3的引用计数:3
ptr4的引用计数:3
ptr5的引用计数:3
*/
如果使用拷贝构造的方式初始化共享智能指针,这两个对象会同时管理同一块内存,堆内存对应的引用计数也会增加,如果使用移动构造的方式初始化智能指针对象,只是转让了内存的所有权,管理内存的对象不会增加,因此内存引用计数不会增加。
make_shared初始化:
#include<iostream>
#include<memory>
using namespace std;
class test {
public:
test() {
cout << "无参构造" << endl;
}
~test() {
cout << "析构函数" << endl;
}
};
int main() {
shared_ptr<int> ptr1 = make_shared<int>(520);
shared_ptr<char> ptr2 = make_shared<char>('s');
shared_ptr<test> ptr3 = make_shared<test>();
cout << "...." << endl;
}
使用reset()初始化
#include<iostream>
#include<memory>
using namespace std;
int main() {
shared_ptr<int> ptr1 = make_shared<int>(520);
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
shared_ptr<int>ptr2 = ptr1;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
ptr2.reset();
cout << "ptr1的引用计数:" << ptr1.use_count() << endl;
cout << "ptr2的引用计数:" << ptr2.use_count() << endl;
shared_ptr<int>ptr3;
ptr3.reset(new int(520));
cout << "ptr3的引用计数:" << ptr3.use_count() << endl;
}
/*
结果:
ptr1的引用计数:1
ptr2的引用计数:2
ptr1的引用计数:1
ptr2的引用计数:0
ptr3的引用计数:1
*/
对于一个为初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会是引用计数减1。
get()函数获取原始指针:
get () 返回存储的指针,而非被管理指针。
int main(){
shared_ptr<int> p(new int);
*p=100;
cout<<*p.get()<<" "<<*p<<endl;
return 0;
}
//100 100
循环引用问题
#include <memory>
class ClassB; // 前置声明
class ClassA {
public:
std::shared_ptr<ClassB> ptrB;
ClassA() {
std::cout << "ClassA constructed" << std::endl;
}
~ClassA() {
std::cout << "ClassA destroyed" << std::endl;
}
};
class ClassB {
public:
std::shared_ptr<ClassA> ptrA;
ClassB() {
std::cout << "ClassB constructed" << std::endl;
}
~ClassB() {
std::cout << "ClassB destroyed" << std::endl;
}
};
int main() {
std::shared_ptr<ClassA> objA = std::make_shared<ClassA>();
std::shared_ptr<ClassB> objB = std::make_shared<ClassB>();
objA->ptrB = objB;
objB->ptrA = objA;
return 0;
}
问题分析
在上述代码中,objA
和 objB
分别持有对 ClassB
和 ClassA
的 std::shared_ptr
。由于 ClassA
的 ptrB
指向 objB
,而 ClassB
的 ptrA
指向 objA
,它们之间形成了循环引用。这意味着它们的引用计数永远不会归零,从而它们的析构函数永远不会被调用,导致内存泄漏。
解决方案
使用 std::weak_ptr
:对于循环引用的其中一个对象,可以将对另一个对象的引用使用 std::weak_ptr
,从而打破循环,避免强引用计数循环增加而导致的内存泄漏。
#include <memory>
class ClassB; // 前置声明
class ClassA {
public:
std::shared_ptr<ClassB> ptrB;
ClassA() {
std::cout << "ClassA constructed" << std::endl;
}
~ClassA() {
std::cout << "ClassA destroyed" << std::endl;
}
};
class ClassB {
public:
std::weak_ptr<ClassA> ptrA; // 使用 weak_ptr
ClassB() {
std::cout << "ClassB constructed" << std::endl;
}
~ClassB() {
std::cout << "ClassB destroyed" << std::endl;
}
};
int main() {
std::shared_ptr<ClassA> objA = std::make_shared<ClassA>();
std::shared_ptr<ClassB> objB = std::make_shared<ClassB>();
objA->ptrB = objB;
objB->ptrA = objA; // 使用 weak_ptr
return 0;
}
weak_ptr(弱引用智能指针)
弱引用智能指针可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会影响引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。
初始化
#include<iostream>
#include<memory>
using namespace std;
int main() {
shared_ptr<int>sp(new int);
weak_ptr<int>wp1;//构造了一个空weak对象
weak_ptr<int>wp2(wp1);//通过一个空的weak构造了另一个空weak对象
weak_ptr<int>wp3(sp);//通过shared对象构造了一个可用的weak对象
weak_ptr<int>wp4;
wp4 = sp;//通过shared对象构造了一个可用的weak实例对象(这是一个隐式类型转换)
weak_ptr<int>wp5;
wp5 = wp3;
}
use_count()
通过调用weak提供的use_count()方法可以获得当前所观测资源的引用计数
weak_ptr不会影响引用计数,只能监测
#include<iostream>
#include<memory>
using namespace std;
int main() {
shared_ptr<int>sp(new int);
weak_ptr<int>wp1;
weak_ptr<int>wp2(wp1);
weak_ptr<int>wp3(sp);
weak_ptr<int>wp4;
wp4 = sp;
weak_ptr<int>wp5;
wp5 = wp3;
cout << wp1.use_count() << endl;
cout << wp2.use_count() << endl;
cout << wp3.use_count() << endl;
cout << wp4.use_count() << endl;
cout << wp5.use_count() << endl;
}
结果:0 0 1 1 1
expired()
可以判断weak所观测的资源是否已经被释放,如果返回值为1则为被释放,返回值为0则没有被释放。
#include<iostream>
#include<memory>
using namespace std;
int main() {
shared_ptr<int>sp(new int);
weak_ptr<int>wp1(sp);
cout << wp1.expired() << endl;
sp.reset();
cout << wp1.expired() << endl;
return 0;
}
结果:0 1
lock()
该函数用来获取所检测资源的shared_ptr对象(也就是返回weak_ptr所检测的指针)
#include<iostream>
#include<memory>
using namespace std;
int main() {
shared_ptr<int>sp1, sp2;
weak_ptr<int>wp;
sp1 = make_shared<int>(520);
wp = sp1;
sp2 = wp.lock();
cout << "use_count:" << wp.use_count() << endl;
sp1.reset();
cout << "use_count:" << wp.use_count() << endl;
sp1 = wp.lock();
cout << "use_count:" << wp.use_count() << endl;
cout << "sp1:" << *sp1 << " " << "sp2:" << *sp2 << endl;
return 0;
}
结果:
use_count:2
use_count:1
use_count:2
sp1:520 sp2:520
reset()
该函数可以清空对象,使其不检测任何资源。
unique_ptr(独占智能指针)
初始化
unique_ptr是一个独占型的智能指针,它不会允许其他的智能指针共享内部的指针,可以通过它的构造函数初始化一个独占智能指针,但不允许通过赋值方式。
#include<iostream>
#include<memory>
using namespace std;
int main() {
unique_ptr<int>ptr1(new int(520));
//unique_ptr<int>ptr2 = ptr1;报错
//unique_ptr 的拷贝构造和赋值运算符被 delete,但是可以使用移动构造
unique_ptr<int>ptr3 = move(ptr1);
}
独占智能指针不允许被复制,但可以通过函数返回给其他的unique_ptr,还可以通过move将其转移给其他的unique_ptr,其根本还是独占一个地址。
#include<iostream>
#include<memory>
using namespace std;
unique_ptr<int> fun() {
unique_ptr<int> a(new int(520));
return a;
}
int main() {
unique_ptr<int>ptr1 = fun();//移动构造
cout << *ptr1 << endl;
return 0;
}
-
使用reset方法可以让unique_ptr解除对原始内存的管理,也可以用初始化一个独占的智能指针
-
如果想要获取独占智能指针管理的原始地址,可以调get()方法