c++ 把内存的控制权对开发人员开放,让程序显式的控制内存,这样能够快速的定位到占用的内存,完成释放的工作。但是这样也会引发一些问题,也就是普通指针的隐患:
出现野指针的有几个地方 :
- 指针声明而未初始化,此时指针的将会随机指向
- 内存已经被释放、但是指针仍然指向它。这时内存有可能被系统重新分配给程序使用,从而会导致无法估计的错误
// 野指针
int * p ;
cout << "p : " << p << endl;
int * p2 = new int(20);
delete p2;
cout << "p2 : " << p2 << endl;
释放已经释放过的内存,或者释放已经被重新分配过的内存,就会导致重复释放错误.
//2. 重复释放
int * p3 = new int(30);
delete p3;
delete p3; //重复delete 会报错。
cout << "p3 : " << p3 << endl;
不再使用的内存,并没有释放,或者忘记释放,导致内存没有得到回收利用。 忘记调用delete
//3. 忘记释放了
int * p4 = new int(40);
C++智能指针是一种特殊类型的指针,其作用是管理动态分配的内存资源,以提供更安全和方便的内存管理。智能指针通过封装原始指针,并在适当的时候自动释放内存,从而避免内存泄漏和悬挂指针等问题。
c++11标准用 unique_ptr
| shared_ptr
| weak_ptr
等指针来自动回收堆中分配的内存。智能指针的用法和原始指针用法一样,只是它多了些释放回收的机制罢了。
智能指针位于memory
头文件中,所以要想使用智能指针,还需要导入这个头文件 #include
int * p = new int(10);
unique_ptr<int> up(p);
智能指针其实是一个类 , 它里面能够包装原始指针。
template <typename _Tp, typename _Dp = default_delete<_Tp> >
class unique_ptr{}
完整代码:
#include
#include
using namespace std;
class Abstract{
public:
Abstract(){
cout <<"Abstract构造" <<endl;
}
~Abstract(){
cout <<"Abstract析构" <<endl;
}
};
void Construct_pointer(){
Abstract * A = new Abstract;
unique_ptr<Abstract> up0 (A);
}
int main() {
cout <<"----------------------" <<endl;
Construct_pointer();
cout <<"----------------------" <<endl;
return 0;
}
unique_ptr 的析构:
~unique_ptr() noexcept
{
auto& __ptr = std::get<0>(_M_t);
if (__ptr != nullptr)
get_deleter()(__ptr);
__ptr = pointer();
}
- 使用智能指针的类在栈内存创建的对象,来包装原始指针。
- 当智能指针的对象销毁了之后, 顺便也会销毁原始指针。
- 智能指针并不是指针,只是包装了指针的类而已。
unique_ptr<Abstract> *up = new unique_ptr<Abstract>(A);
delete up; //报错
使用智能指针的类在堆内存创建的对象,来包装原始指针,会报错。
智能指针把裸指针包装起来,在构造函数里初始化,在析构函数里释放。这样当对象失效销毁时,C++ 就会自动调用析构函数,完成内存释放、资源回收等清理工作。它实践了 RAII(Resource Acquisition Is Initialization),包装了裸指针,而且因为重载了 *
和 ->
操作符,用起来和原始指针一模一样。
unique_ptr
是一个独享所有权的智能指针,它提供了严格意义上的所有权。也就是只有这个指针能够访问这片空间,不允许拷贝,但是允许移动(转让所有权)。
unique_ptr
在声明的时候必须用模板参数指定类型。
int * p = new int(10);
unique_ptr<int> ptr0(p);
//unique_ptr up1(p); //禁止多个智能智能包装同一个原始指针
unique_ptr<int> ptr1(new int(20));
unique_ptr<std::string> ptr2(new string("hello")); // string智能指针
//unique_ptr up2 = up; // 禁止拷贝 Call to deleted constructor of 'unique_ptr'
unique_ptr<int> up3 = move(ptr0); // 可以移动
//or
// 工厂函数创建智能指针
//std::unique_ptr ptr5 = make_unique(36);
auto ptr5 = make_unique<int>(36);
assert(ptr5 && *ptr5 == 36); // 此时智能指针有效
auto ptr6 = std::move(ptr5); // 使用move()转移所有权
assert(!ptr5 && ptr6); // ptr1变成了空指针
int * p2 = ptr0.get();
cout << "p2解引用:" << *p2 <<endl;
重载 *
typename add_lvalue_reference<element_type>::type
operator*() const
{
_GLIBCXX_DEBUG_ASSERT(get() != pointer());
return *get();
}
#include
cout << "智能指针来获取原始数据 " << *ptr0 <<endl;
assert(*ptr0 == 10);
assert(*ptr2 == "hello"); // 可以使用*取内容
assert(ptr2->size() == 5);
ptr0.reset(); // delete 原始指针,delete p
//or
int * p3 = new int(30);
ptr0.reset(p3) ; //delete掉 p 然后重新包装p3.
assert(*ptr0 == 30);
ptr0++; // 导致编译错误
ptr1 += 2; // 导致编译错误
unique_ptr
表示空指针,这样就相当于直接操作了空指针,运行时就会产生致命的错误(比如 core dump)。unique_ptr<float> ptr3;
cout << "使用智能指针来获取原始数据 " << *ptr3 <<endl;
为了避免错误,可以调用工厂函数 make_unique()
,强制创建智能指针的时候必须初始化。
#include
auto ptr3 = make_unique<int>(36); // 工厂函数创建智能指针
assert(ptr3 && *ptr3 == 36);
auto ptr4 = make_unique<string>("world"); // 工厂函数创建智能指针
assert(!ptr4->empty());
如果出现错误,参考: 关于c ++:error :: make_unique不是“ std”的成员
或者在代码前面添加:
template<typename T, typename... Args> // 可变参数模板
std::unique_ptr< T > // 返回智能指针
make_unique(Args&&... args) {
return std::unique_ptr< T >(new T(std::forward<Args>(args)...));
}
#include
#include
/*
练习: 使用unique_ptr来管理friar的指针
*/
using namespace std;
template<typename T, typename... Args> // 可变参数模板
std::unique_ptr< T > // 返回智能指针
make_unique(Args&&... args) {
return std::unique_ptr< T >(new T(std::forward<Args>(args)...));
}
class friar{
public:
string name;
friar(string name) : name(name){
cout << "friar的构造..." <<endl;
}
~friar (){
cout << "friar的析构..." <<endl;
}
void travel(){
cout << name << " 在游历..." <<endl;
}
};
int main() {
//方法一
friar * f = new friar("张三丰");
unique_ptr<friar> ptr(f);
//方法二
// unique_ptr ptr;
// ptr.reset(f);
//方法三
//friar f("张三丰");
//unique_ptr ptr = make_unique(f);
//4. 通过智能指针来调用管理的那个指针指向的位置里面存放的对象的成员函数travel.
friar * ff = ptr.get();
ff->travel();
//---------先解引用--------------
(*ptr).travel();
//---------直接调用---------
ptr->travel();
return 0;
}
这里重载 ->
:
pointer
operator->() const noexcept
{
_GLIBCXX_DEBUG_ASSERT(get() != pointer());
return get();
}
shared_ptr
: 允许多个智能指针共享同一块内存,由于并不是唯一指针,所以为了保证最后的释放回收,采用了计数处理,每一次的指向计数 + 1 , 每一次的 reset会导致计数 -1 ,直到最终为0 ,内存才会最终被释放掉。 可以使用use_cout
来查看目前的指针个数。
shared_ptr
智能指针#include
#include
#include
using namespace std;
int main() {
shared_ptr<int> ptr1(new int(10)); // int智能指针
assert(*ptr1 == 10); // 可以使用*取内容
shared_ptr<string> ptr2(new string("hello")); // string智能指针
assert(*ptr2 == "hello"); // 可以使用*取内容
auto ptr3 = make_shared<int>(42); // 工厂函数创建智能指针
assert(ptr3 && *ptr3 == 42); // 可以判断是否为空指针
auto ptr4 = make_shared<string>("world"); // 工厂函数创建智能指针
assert(!ptr4->empty()); // 可以使用->调用成员函数
return 0;
}
shared_ptr
指向计数shared_ptr
与 unique_ptr
的不同点:同一块内存的所有权是可以被安全共享的,也就是说支持拷贝赋值,允许被多个共享指针同时持有。#include
#include
#include
using namespace std;
int main() {
int * p = new int(12);
shared_ptr <int> sp1(p); //共享指针
cout <<"查看计数sp1 :" << sp1.use_count() << endl;
cout << "-----------------------" << endl;
shared_ptr <int> sp2 (sp1);
cout <<"查看计数sp1 :" << sp1.use_count() << endl;
cout <<"查看计数sp2 :" << sp2.use_count() << endl;
//shared_ptr sp03 = sp01; //可以拷贝,但是看起来拷贝之后计数没有增加。
shared_ptr <int> sp3 {sp2};
cout << "-----------------------" << endl;
cout <<"查看计数sp1 :" << sp1.use_count() << endl;
cout <<"查看计数sp2 :" << sp2.use_count() << endl;
cout <<"查看计数sp3 :" << sp3.use_count() << endl;
cout << "-----------------------" << endl;
sp1.reset();
cout <<"查看计数sp1 :" << sp1.use_count() << endl;
cout <<"查看计数sp2 :" << sp2.use_count() << endl;
cout <<"查看计数sp3 :" << sp3.use_count() << endl;
cout << "***********************" << endl;
sp2.reset();
cout <<"查看计数sp1 :" << sp1.use_count() << endl;
cout <<"查看计数sp2 :" << sp2.use_count() << endl;
cout <<"查看计数sp3 :" << sp3.use_count() << endl;
cout << ">>>>>>>>>>>>>>>>>>>>>>" << endl;
sp3.reset();
cout <<"查看计数sp1 :" << sp1.use_count() << endl;
cout <<"查看计数sp2 :" << sp2.use_count() << endl;
cout <<"查看计数sp3 :" << sp3.use_count() << endl;
return 0;
}
运行结果:
查看计数sp1 :1
-----------------------
查看计数sp1 :2
查看计数sp2 :2
-----------------------
查看计数sp1 :3
查看计数sp2 :3
查看计数sp3 :3
-----------------------
查看计数sp1 :0
查看计数sp2 :2
查看计数sp3 :2
***********************
查看计数sp1 :0
查看计数sp2 :0
查看计数sp3 :1
>>>>>>>>>>>>>>>>>>>>>>
查看计数sp1 :0
查看计数sp2 :0
查看计数sp3 :0
引用计数最开始的时候是 1,表示只有一个持有者。如果发生拷贝赋值——也就是共享的时候,引用计数就增加,而发生析构销毁的时候,引用计数就减少。只有当引用计数减少到 0,也就是说,没有任何人使用这个指针的时候,它才会真正调用 delete 释放内存。
因为 shared_ptr 具有完整的“值语义”(即可以拷贝赋值),所以,它可以在任何场合替代原始指针,而不用再担心资源回收的问题,比如用于容器存储指针、用于函数安全返回动态创建的对象,等等。
对于引用计数法实现的计数,总是避免不了循环引用(或环形引用)的问题,即我中有你,你中有我,shared_ptr
也不例外。如果两个对象内部的智能指针互相指向了对方,导致自己的引用计数一直为1,所以没有进行析构,这就造成了内存泄漏。
#include
#include
#include
using namespace std;
class A;
class B{
public:
shared_ptr<A> spa;
void linkA( shared_ptr<A> sp){
spa = sp;
}
B(){
cout << "B 构造..." << endl;
};
~B(){
cout << "B 析构~~~" << endl;
};
};
class A{
public:
shared_ptr<B> spb;
void linkB( shared_ptr<B> sp){
spb = sp;
}
A(){
cout << "A 构造..." << endl;
};
~A(){
cout << "A 析构 ~~~" << endl;
};
};
int main() {
B * b = new B;
A * a = new A;
shared_ptr<B> sp_b(b);
shared_ptr<A> sp_a(a);
// b->linkA(sp_a);
// a->linkB(sp_b);
return 0;
}
运行结果:
B 构造...
A 构造...
A 析构 ~~~
B 析构~~~
如果不注释最后两行:
int main() {
B * b = new B;
A * a = new A;
shared_ptr<B> spb(b);
shared_ptr<A> spa(a);
b->linkA(spa);
a->linkB(spb);
return 0;
}
则运行结果是:
B 构造...
A 构造...
没有执行析构,指针没有销毁,计数不为0,原因解析如下图:
此时指向A 的智能指针 2 个,指向B的智能指针 2 个
当代码运行完,栈内存销毁,此时:
此时指向A 的智能指针 1 个,指向B的智能指针 1 个
为了避免shared_ptr
的环形引用问题,需要引入一个弱指针 weak_ptr
,它指向一个由 shared_ptr
管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr
绑定到一个shared_ptr
不会改变shared_ptr
的引用计数。不论是否有weak_ptr
指向,一旦最后一个指向对象的shared_ptr
被销毁,对象就会被释放。从这个角度看,weak_ptr
更像是shared_ptr
的一个助手而不是智能指针。
weak_ptr
不包装原始指针,它包装的是智能指针 (weak_ptr | shared_ptr)
#include
#include
#include
using namespace std;
class stu;
int main() {
int * p = new int (36);
//共享指针
shared_ptr<int> sp(p);
cout << "sp count : " << sp.use_count() << endl;
//弱指针
weak_ptr<int> wp(sp);
cout <<"wp count : "<< wp.use_count() << endl;
//只能得到包装的共享指针。
shared_ptr<int> sp1 = wp.lock();
cout << "p value : " << *sp1 << endl;
cout << "p value : " << *(wp.lock()) << endl;
cout << "wp cout : " << wp.lock().use_count() << endl;
cout << "sp1 cout : " << sp1.use_count() << endl;
cout << "sp cout : " << sp.use_count() << endl;
return 0;
}
运行结果:
sp count : 1
wp count : 1
p value : 36
p value : 36
wp cout : 3
sp1 cout : 2
sp cout : 2
#include
#include
#include
using namespace std;
class A;
class B{
public:
weak_ptr<A> spa;
void linkA( shared_ptr<A> sp){
spa = sp;
}
B(){
cout << "B 构造..." << endl;
};
~B(){
cout << "B 析构~~~" << endl;
};
};
class A{
public:
weak_ptr<B> spb;
void linkB( shared_ptr<B> sp){
spb = sp;
}
A(){
cout << "A 构造..." << endl;
};
~A(){
cout << "A 析构~~~" << endl;
};
};
int main() {
B * b = new B;
A * a = new A;
shared_ptr<B> sp_b(b);
shared_ptr<A> sp_a(a);
b->linkA(sp_a);
a->linkB(sp_b);
return 0;
}
运行结果:
B 构造...
A 构造...
A 析构~~~
B 析构~~~
#include
#include
int main() {
std::unique_ptr<int[]> arr(new int[5]); // 创建一个包含5个int元素的动态数组
for (int i = 0; i < 5; ++i) {
arr[i] = i; // 对数组进行赋值
}
// 使用数组
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
return 0;
}
#include
#include
class MyClass {
public:
void DoSomething() { /* ... */ }
~MyClass(){
std::cout << "MyClass 析构~~~" << std::endl;
}
};
int main() {
std::unique_ptr<MyClass> ptr(new MyClass()); // 创建一个唯一拥有权的智能指针
ptr->DoSomething(); // 使用ptr调用成员函数
return 0; // 当ptr超出作用域时,MyClass对象将自动销毁
}
#include
#include
class MyResource {
public:
MyResource() { std::cout << "Acquiring resource." << std::endl; }
~MyResource() { std::cout << "Releasing resource." << std::endl; }
void DoSomething() {
// 执行一些操作
throw std::runtime_error("Some error occurred.");
}
};
int main() {
try {
std::unique_ptr<MyResource> ptr(new MyResource()); // 创建一个拥有权的智能指针
ptr->DoSomething(); // 这里会抛出一个异常,但由于智能指针的析构函数会被调用,资源会得到正确释放
} catch (const std::exception& e) {
std::cout << "Exception occurred: " << e.what() << std::endl;
}
return 0;
}