目录
自己管理指针会有什么潜在的问题
-
内存泄漏:
最常见的问题就是内存泄漏,如果动态分配的内存没有被正确的释放,就是说没有被正确的delete操作就会导致内存泄漏。会使程序消耗的内存越来越多,最终导致程序的崩溃和性能的下降。
-
悬挂指针:
在手动管理内存的时候会出现悬挂指针的问题,就是指向的内存已经被释放,但是指针的本身仍然保留,当我们再次访问这个指针的时候就会导致未定义的行为,可能会引发程序的崩溃。
-
二次释放:
如果同一个内存地址被释放两次(二次释放),会导致严重的运行时错误,破坏堆的内部结构,也可能导致程序崩溃。
-
资源泄漏:
除了内存泄漏外,还可能存在其他资源泄漏,比如文件句柄、数据库连接等资源没有正确释放,导致系统资源的浪费。
智能指针的作用
C++11引入了智能指针存储指向动态分配对象指针的类,
用于生存期的控制,能够确保在离开指针所在作用域时,自动的销毁分配的对象,防止内存泄漏。智能指针通过封装指针、使用 RAII(资源获取即初始化)的原则,以及引用计数等机制来实现自动化的内存管理。
智能指针的核心实现技术是引用技术,每使用它一次内部引用计数加1,每析构一次内部引用计数减1,减为0时,删除原始指针指向的堆区内存,使用智能指针需要引用头文件
RAII的原理
RAII(Resource Acquisition Is Initialization)是一种C++编程中的重要原则,它基于栈对象的生命周期来管理资源的获取和释放。在RAII中,资源的获取和释放被绑定到对象的生命周期,当对象被创建时获取资源,当对象离开作用域时自动释放资源,从而确保资源的正确释放,避免资源泄漏。
share_ptr(共享智能指针)
共享智能指针允许多个智能指针共享同一资源,通过引用计数来管理资源的生命周期,适用于需要共享资源的场景。
共享智能指针对象初始化完毕之后就指向了要管理的堆区内存,可以使用共享智能指针的成员函数 use_count 来查看当前有多少指针共同管理这这块内存
初始化的方式
-
通过构造函数初始化
#include<iostream> #include<memory> using namespace std; int main() { // 使用智能指针管理一块 int 型的堆内存 shared_ptr<int> ptr1(new int(520)); cout << "ptr1管理的内存引用计数:" << ptr1.use_count() << endl; //使用智能指针管理一块字符数组对应的堆内存 shared_ptr<char> ptr2(new char[520]); cout << "ptr2管理的内存引用计数:" << ptr2.use_count() << endl; shared_ptr<int> ptr3; cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; // 创建智能指针对象, 初始化为空 shared_ptr<int> ptr4(nullptr); cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; /*打印结果如下: ptr1管理的内存引用计数 : 1 ptr2管理的内存引用计数 : 1 ptr3管理的内存引用计数 : 0 ptr4管理的内存引用计数 : 0*/ return 0; }
从上面的程序我们能看出来,如果智能指针初始化了一块有效的内存,那么这块内存的引用计数+1,而如果没有被初始化或者是被初始化为一个空的指针,那么这个智能指针的引用计数还是0。
注意
不要用一个原始指针初始化多个shared_ptr
这是因为,shared_ptr通过引用计数来管理资源,多个shared_ptr实例共享相同的资源,并且会增加引用计数。但是多个shared_ptr实例指向同一个原始指针,这些shared_ptr指针是不知道他们的其他shared_ptr共享共同的指针。这样既可能出现悬挂指针的问题和指针的多次释放问题。
可以使用下面的方法,共享同一个指针,下面的sharedPtr1 和 sharedPtr2 共享相同的引用计数和资源,不会出现引用计数管理不当的问题。
std::shared_ptr sharedPtr1 = std::make_shared(10);
std::shared_ptr sharedPtr2 = sharedPtr1; // 这是安全的,因为它们共享相同的源
- 拷贝构造、移动构造初始化
-
#include<iostream> #include<memory> using namespace std; int main() { //构造函数 shared_ptr ptr1(new int(520)); cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; //拷贝构造函数 shared_ptr ptr2(ptr1); cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; shared_ptr ptr3 = ptr1; cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; //移动构造函数 shared_ptr ptr4(std::move(ptr1)); cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; std::shared_ptr ptr5 = std::move(ptr2); cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl; /*打印结果如下: ptr1管理的内存引用计数 : 1 ptr2管理的内存引用计数 : 2 ptr3管理的内存引用计数 : 3 ptr4管理的内存引用计数 : 3 ptr5管理的内存引用计数 : 3*/ return 0; }
拷贝构造函数和赋值构造都会增加引用计数,这都是多个shared_ptr共享相同的资源。
但是移动构造就是会将资源的所有权从一个shared_ptr转移到另一个,被移动的shared_ptr会指向空,所以引用计数不增加。
-
std::make_shared辅助函数
通过c++11提供的std::make_shared()函数就可以完成内存对象的创建并将其初始化给智能指针
#include <iostream> #include <string> #include <memory> using namespace std; class Test { public: Test() { cout << "无参构造函数" << endl; } Test(int x) { cout << "int类型构造函数 " << x << endl; } Test(string str) { cout << "string类型的构造函数" << str << endl; } ~Test() { cout << "析构函数" << endl; } }; int main() { // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1 shared_ptr<int> ptr1 = make_shared<int>(520); cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; shared_ptr<Test> ptr2 = make_shared<Test>(); cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; shared_ptr<Test> ptr3 = make_shared<Test>(520); cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; shared_ptr<Test> ptr4 = make_shared<Test>("QQQQ"); cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; return 0; } 打印结果如下: ptr1管理的内存引用计数: 1 无参构造函数 ptr2管理的内存引用计数: 1 int类型构造函数 520 ptr3管理的内存引用计数: 1 string类型的构造函数QQQQ ptr4管理的内存引用计数: 1 析构函数 析构函数 析构函数
make_shared()函数原型
std::shared_ptr<T> make_shared< T >( Args&&... args );
T 表示指向的类型,Args 是类型 T 的构造函数所需的参数列表,在调用时需要传递给构造函数。
- reset方法
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
shared_ptr<int> ptr1 = make_shared<int>(520);
shared_ptr<int> ptr2 = ptr1;
shared_ptr<int> ptr3 = ptr1;
shared_ptr<int> ptr4 = ptr1;
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
ptr4.reset();
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
shared_ptr<int> ptr5;
ptr5.reset(new int(250));
cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
return 0;
}
打印结果如下:
ptr1管理的内存引用计数: 4
ptr2管理的内存引用计数: 4
ptr3管理的内存引用计数: 4
ptr4管理的内存引用计数: 4
ptr1管理的内存引用计数: 3
ptr2管理的内存引用计数: 3
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 0
ptr5管理的内存引用计数: 1
reset()函数就是让智能指针初始化,如果这个智能指针当中存的值,那么调用reset()的时候就会释放原来的空间,使引用计数-1
shared_ptr 获取原始指针
使用get()函数返回原始指针
int main()
{
shared_ptr<int> p(new int);
*p = 100;
cout << *p.get() << " " << *p << endl;
return 0;
}
//100 100
weak_ptr (弱引用智能指针)
弱引用类型的指针就是一个特殊的智能指针,它可以允许你观察和访问有强引用智能指针管理的对象,但不会增加对象的引用计数。
我们可以把weak_ptr看作是shared_ptr指针的助手,他不管理shared_ptr内部的指针。不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数。它的主要作用就是作为旁观者监视shared_ptr指针管理的资源是否存在。
初始化的方式
构造函数、拷贝构造
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> sp(new int);
//weak_ptr<int> wp1; 构造了一个空 weak_ptr 对象
weak_ptr<int> wp1;
// weak_ptr<int> wp2(wp1); 通过一个空 weak_ptr 对象构造了另一个空 weak_ptr 对象
weak_ptr<int> wp2(wp1);
//weak_ptr<int> wp3(sp); 通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象
weak_ptr<int> wp3(sp);
//wp4 = sp; 通过一个 shared_ptr 对象构造了一个可用的 weak_ptr 实例对象(这是一个隐式类型转换)
weak_ptr<int> wp4;
wp4 = sp;
//wp5 = wp3; 通过一个 weak_ptr 对象构造了一个可用的 weak_ptr 实例对象
weak_ptr<int> wp5;
wp5 = wp3;
return 0;
}
use_count()
use_count的方法可以获得当前所观测的引用计数。和shared_ptr的count计数作用一样。
#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 << "use_count: " << endl;
cout << "wp1: " << wp1.use_count() << endl;
cout << "wp2: " << wp2.use_count() << endl;
cout << "wp3: " << wp3.use_count() << endl;
cout << "wp4: " << wp4.use_count() << endl;
cout << "wp5: " << wp5.use_count() << endl;
return 0;
}
/* use_count:
wp1: 0
wp2: 0
wp3: 1
wp4: 1
wp5: 1*/
通过上面的代码看出,虽然弱引用智能指针wp3,wp4,wp5都是检测的同一个资源,但是没有像shared_ptr一样,随着检测增多,而增加引用计数。这也说shared_ptr只是监测资源,而不是管理资源的。
expired()
expired()方法可以判断所监测的资源是否已经被释放。当shared_ptr管理的资源被释放或者是调用了reset()方法,就会返回 true 表示所监测的资源不存在了。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> shared(new int(10));
weak_ptr<int> weak(shared);
cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;
shared.reset();
cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;
return 0;
}
//1. weak is not expired
//2. weak is expired
lock()
通过调用 std::weak_ptr 类提供的 lock() 方法来获取管理所监测资源的 shared_ptr 对象
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> sp1, sp2;
weak_ptr<int> wp;
sp1 = std::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 << endl;
cout << "*sp2: " << *sp2 << endl;
return 0;
}
//use_count: 2
// use_count : 1
// use_count : 2
// * sp1 : 520
// * sp2 : 520
此代码中,sp2 = wp.lock(); 通过调用 lock() 方法得到一个用于管理 weak_ptr 对象所监测的资源的共享智能指针对象,使用这个对象初始化 sp2,此时所监测资源的引用计数为 2。
sp1.reset(); 共享智能指针 sp1 被重置,weak_ptr 对象所监测的资源的引用计数减 1。
sp1 = wp.lock(); sp1 重新被初始化,并且管理的还是 weak_ptr 对象所监测的资源,因此引用计数加 1。
共享智能指针对象 sp1 和 sp2 管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是 520。
reset()
用来清空weak_ptr对象,使其不监测任何对象。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << "1. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;
wp.reset();
cout << "2. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;
return 0;
}
1. wp is not expired
2. wp is expired
返回管理this的shared_ptr
在 getSharedPtr()
函数中,你直接返回了一个 shared_ptr<Test>
,并传入了指向当前对象的裸指针 this
。然而,当使用 shared_ptr
构造函数直接接受一个裸指针时,这个 shared_ptr
并不知道它是否已经被其他 shared_ptr
管理。这可能导致资源释放的不确定性和重复释放的问题。
#include <iostream>
#include <memory>
using namespace std;
struct Test
{
shared_ptr<Test> getSharedPtr()
{
return shared_ptr<Test>(this);
}
~Test()
{
cout << "析构函数" << endl;
}
};
int main()
{
shared_ptr<Test> sp1(new Test);
cout << "引用个数 " << sp1.use_count() << endl;
shared_ptr<Test> sp2 = sp1->getSharedPtr();
cout << "引用个数: " << sp1.use_count() << endl;
return 0;
}
//引用计数: 1
//引用计数: 1
//析构函数
//析构函数
通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针 this 构造了两个智能指针对象 sp1 和 sp2,这二者之间是没有任何关系的,因为 sp2 并不是通过 sp1 初始化得到的实例对象。在离开作用域之后 this 将被构造的两个智能指针各自析构,导致重复析构的错误。
循环引用问题
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A
{
public:
shared_ptr<B> bptr;
~A()
{
cout << "class TA is disstruct ..." << endl;
}
};
class B
{
public:
shared_ptr<A> aptr;
~B()
{
cout << "class TB is disstruct ..." << endl;
}
};
void testPtr()
{
shared_ptr<A> ap(new A);
shared_ptr<B> bp(new B);
cout << "A 的 引用计数: " << ap.use_count() << endl;
cout << "B 的 引用计数: " << bp.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "A 的 引用计数: " << ap.use_count() << endl;
cout << "B 的 引用计数: " << bp.use_count() << endl;
}
int main()
{
testPtr();
return 0;
}
A 的 引用计数: 1
B 的 引用计数: 1
A 的 引用计数: 2
B 的 引用计数: 2
共享智能指针 ap、bp 对 A、B 实例对象的引用计数变为 2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类 A、B 的实例对象不能被析构,最终造成内存泄露。
图释循环引用:
用图片解释就是这样:
当释放的时候:
解决方法:
通过使用 weak_ptr 可以解决这个问题,只需要将类 A 或者 B 的任意一个成员改为 weak_ptr.
由于weak_ptr不会增加shared_ptr的引用计数,所以A和B 中有一个的引用计数为1,在pa和pb析构时,会正确地释放掉内存
下面程序中,在对类 A 成员赋值时 ap->bptr = bp; 由于 bptr 是 weak_ptr 类型,这个赋值操作并不会增加引用计数,所以 bp 的引用计数仍然为 1,在离开作用域之后 bp 的引用计数减为 0,类 B 的实例对象被析构。
在类 B 的实例对象被析构的时候,内部的 aptr 也被析构,其对 A 对象的管理解除,内存的引用计数减为 1,当共享智能指针 ap 离开作用域之后,对 A 对象的管理也解除了,内存的引用计数减为 0,类 A 的实例对象被析构。
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A
{
public:
weak_ptr<B> bptr;
~A()
{
cout << "A 的 析构函数" << endl;
}
};
class B
{
public:
shared_ptr<A> aptr;
~B()
{
cout << "B 的 析构函数" << endl;
}
};
void testPtr()
{
shared_ptr<A> ap(new A);
shared_ptr<B> bp(new B);
cout << "A 的 引用计数: " << ap.use_count() << endl;
cout << "B 的 引用计数: " << bp.use_count() << endl;
ap->bptr = bp;
bp->aptr = ap;
cout << "A 的 引用计数: " << ap.use_count() << endl;
cout << "B 的 引用计数: " << bp.use_count() << endl;
}
int main()
{
testPtr();
return 0;
}
A 的 引用计数: 1
B 的 引用计数: 1
A 的 引用计数: 2
B 的 引用计数: 1
B 的 析构函数
A 的 析构函数
unique_ptr(独占智能指针)
初始化
std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
// 通过构造函数初始化对象
unique_ptr<int> ptr1(new int(10));
// 报错
unique_ptr<int> ptr2 = ptr1;
return 0;
}
unique_ptr
是一种独占所有权的智能指针,它确保在任何时候只有一个指针可以拥有对所指向对象的所有权。因此,它不允许被复制,但可以通过函数返回给其他的unique_ptr
或者通过std::move()
转移给其他的unique_ptr
。这样发生的是所有权的转移。也就是说,函数中的
unique_ptr
将其所指向的对象所有权转移给了调用该函数的地方。这意味着函数结束后,原始的unique_ptr
将不再拥有对象的所有权,因为所有权已经转移到了函数外的另一个unique_ptr
中。当使用
std::move()
将一个unique_ptr
转移给另一个unique_ptr
时,也发生了所有权的转移。通过std::move()
,告诉编译器你要移动对象的所有权,而不是复制它。这样做不会创建对象的拷贝,而是将原始unique_ptr
指向的对象所有权转移到新的unique_ptr
中。原始的unique_ptr
将不再拥有对象的所有权,而新的unique_ptr
则成为对象的唯一所有者。
reset()
使用 reset 方法可以让 unique_ptr 解除对原始内存的管理,也可以用来初始化一个独占的智能指针。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 ;
ptr1.reset(); //解除对原始内存的管理
ptr2.reset(new int(250)); //重新指定智能指针管理的原始内存
return 0;
}
如果想要获取独占智能指针管理的原始地址,可以调用 get () 方法
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = move(ptr1);
ptr2.reset(new int(251));
cout << *ptr2.get() << endl; // 得到内存地址中存储的实际数值 250
return 0;
}
251
shared_ptr的实现
实现1:
#pragma once
#include<iostream>
using namespace std;
//实现方案一
//使用辅助类Ref管理引用计数
template<class T>
class Ref {
private:
//计数变量
int r_count = 0;
//存放原始指针
T* object;
public:
//构造函数
Ref(T* target) :object(target) {
r_count++;
}
//频繁调用使用inline函数,减少函数调用的开销
//引用计数+1
inline void increase() {
r_count++;
}
//引用计数-1并且要判断是否要释放原始堆区内存
inline void reduce() {
r_count--;
//如果引用计数为0
//释放管理的堆区内存和自己
if (r_count == 0) {
delete object;
delete this;
}
}
T* get() {
return object;
}
int getCount() {
return r_count;
}
};
//共享智能指针需要的方法:
/*
无参构造,传递指针构造,拷贝构造,移动构造,拷贝赋值,移动赋值
reset()替换对象 reset()销毁对象
operator*() operator->()
get()获取原始指针
use_count 获得引用计数
*/
template <class T>
class Share_ptr1
{
private:
Ref<T>* ref = nullptr;
public:
//默认构造函数默认初始化
Share_ptr1() = default;
~Share_ptr1()
{
if (ref)ref->reduce();//引用计数-1
}
Share_ptr1(T* newP) {
cout << "调用构造函数" << endl;
ref = new Ref<T>(newP);
}
Share_ptr1(const Share_ptr1& other) {
cout << "调用拷贝构造函数" << endl;
this->ref = other.ref;
if (ref)ref->increase();
}
Share_ptr1(Share_ptr1&& other) {
cout << "调用移动构造函数" << endl;
ref = other.ref;
other.ref = nullptr;
}
Share_ptr1& operator = (const Share_ptr1 & other) {
cout << "调用赋值函数" << endl;
//因为要进行赋值操作,所以之前如果指向一个地址的话,
//那个指针的引用计数要-1
if (ref)ref->reduce();
ref = other.ref;
if (ref)ref->increase();
return *this;
}
Share_ptr1& operator =(Share_ptr1 && other) {
cout << "调用移动赋值函数" << endl;
if (ref)ref->reduce();
ref = other.ref;
other.ref = nullptr;
return *this;
}
void reset(T* newP) {
if (ref)ref->reduce();
ref = new Ref<T>(newP);
}
void reset() {
if (ref)ref->reduce();
ref = nullptr;
}
//*要返回对象类型的
T& operator *() {
if (ref) return *ref->get();
}
//->是对指针操作,要返回指针类型
T* operator ->() {
if (ref) return ref->get();
}
int use_count() {
if (ref)return ref->getCount();
return 0;
}
};
实现2:
#pragma once
#include<iostream>
using namespace std;
template<class T>
class Share_ptr2 {
private:
T* data_ = nullptr;
size_t* count_ = nullptr;
private:
//释放资源
void release() {
if (count_) {
--(*count_);
if (*count_ == 0) {
delete count_;
delete data_;
}
count_ = nullptr;
data_ = nullptr;
}
}
public:
//构造函数
Share_ptr2(T* ptr = nullptr) {
if (ptr) {
count_ = new size_t(1);
data_ = ptr;
}
}
//拷贝构造
Share_ptr2(const Share_ptr2<T>& other) {
count_ = other.count_;
data_ = other.data_;
if (count_) {
++(*count_);
}
}
//移动构造
Share_ptr2(Share_ptr2<T>&& other) {
count_ = other.count_;
data_ = other.data_;
other.count_ = nullptr;
other.data_ = nullptr;
}
~Share_ptr2()
{
release();
}
//赋值运算符
Share_ptr2<T>& operator=(const Share_ptr2<T>&other){
if (this != &other) {
this->release();
count_ = other.count_;
data_ = other.data_;
if (count_) {
++(*count_);
}
}
return *this;
}
T& operator*() const {
return *data_;
}
T* operator->() const {
return data_;
}
size_t use_count() const {
return count_ ? *count_ : 0;
}
void reset(T* ptr = nullptr) {
this->release();
if (ptr) {
count_ = new size_t(1);
data_ = ptr;
}
}
};
标签:count,weak,智能,详解,shared,include,ptr,指针
From: https://blog.csdn.net/2201_75839679/article/details/137235127