[c++]关于c++11的智能指针详细解析

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(共享智能指针)

初始化

共享智能指针是指多个智能指针可以同时管理同一块有效内存,共享智能指针是一个模板类,如果进行初始化有三种方式:

  1. 通过构造函数初始化

  2. std::make_share 辅助函数

  3. 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;
}

问题分析

在上述代码中,objAobjB 分别持有对 ClassBClassAstd::shared_ptr。由于 ClassAptrB 指向 objB,而 ClassBptrA 指向 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()方法

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值