智能指针
一、两种智能指针
智能指针是C++11标准引入的一种管理动态内存的工具,用于自动管理动态分配的内存,
避免内存泄漏和悬空指针问题。C++11标准提供了三种智能指针:
unique_ptr、shared_ptr和weak_ptr。
shared_ptr
允许多个指针指向同一个对象。
unique_ptr
独占所指向的对象,尽量使用unique_ptr,资源占用少,效率更高。
weak_ptr
是一种弱引用,指向shared_ptr
所管理的对象,但不增加引用计数。
二、共享智能指针 shared_ptr
1、是什么:
shared_ptr共享它指向的对象,多个shared_ptr可以指向同一个对象。
在内部采用技术机制实现。
当新的shared_ptr指向一个对象时,引用计数会增加1;当shared_ptr销毁时,引用计数会减少1。
当计数变为0时该对象的shared_ptr被销毁时,该对象会被自动删除。
2、初始化方法:
-
方法一:使用new关键字,分配内存并初始化
shared_ptr <类名> 指针名(new 类名(参数));
shared_ptr<A> p1(new A(10));
-
方法二:使用make_shared函数,C++11标准
shared_ptr <类名> 指针名 = make_shared<类名>(参数);
shared_ptr<A> p2 = make_shared<A>(10);
-
方法三:用已存在的地址
A *p = new A(10); shared_ptr <A> sp(p);
-
方法四:拷贝构造和赋值
shared_ptr<A> p(new A(10)); shared_ptr<A> p1 = p; //计数器+1 shared_ptr<A> p2(p); //计数器+1
3、使用方法
-
智能指针重载了 * 和 -> 操作符,可以直接使用指针访问对象。
-
计数器的值: use_count()
-
支持普通的拷贝和赋值,左值的shared_ptr的计数器会 +1,右值的shared_ptr的计数器会 -1。
-
unique()方法,如果use_count()返回1,则返回true,否则返回false。
-
get()方法,返回原始指针。
#include<iostream> #include<memory> using namespace std; class A { public: A() { std::cout << "A()" << std::endl; } ~A() { std::cout << "~A()" << std::endl; } A(const int a):_a(a) { cout << "a = " << a << endl; } int _a; }; int main() { //方法一,使用new初始化 shared_ptr<A> p1(new A(10)); shared_ptr<A> p7 = p1; //方法二,使用make_shared初始化 shared_ptr<A> p2 = make_shared<A>(20); //方法三,使用已存在的指针初始化 A *p3 = new A(30); shared_ptr<A> p4(p3); //方法四,拷贝构造和赋值 shared_ptr<A> p5 = p4; shared_ptr<A> p6(p5); cout << "之前p1: " << p1.use_count() << endl; //2 p7 = p4; cout << "之后p1: " << p1.use_count() << endl; //此处减少了一个指向p1的指针,所以变为1 cout << "之后p1的unique()方法:" << p1.unique() << endl; //1,true cout << "p4: " << p4.use_count() << endl; //下面都指向同一个指针,所以计数器都为4 cout << "p4的unique()方法:" << p4.unique() << endl; //0,false cout << "p5: " << p5.use_count() << endl; //4 cout << "p6: " << p6.use_count() << endl; //4 return 0; }
-
不要用同一个裸指针初始化多个智能指针,否则会导致重复删除同一块内存的问题。
#include <iostream> #include <memory> class A { public: A() { std::cout << "A constructor" << std::endl; } ~A() { std::cout << "A destructor" << std::endl; } }; int main() { A* rawPtr = new A(); // 裸指针 std::shared_ptr<A> ptr1(rawPtr); // 第一个 shared_ptr std::shared_ptr<A> ptr2(rawPtr); //第二个 shared_ptr,使用同一个裸指针初始化 //当 ptr1 和 ptr2 超出作用域时,它们的析构函数都会被调用, // 从而导致对同一块内存(rawPtr 指向的内存)进行两次删除操作。 return 0; }
原因如下:
ptr1 和 ptr2 都用同一个裸指针 rawPtr 来初始化。由于 shared_ptr 的引用计数机制是基于构造函数来增加引用计数的,而这里有两个 shared_ptr 实例指向同一块内存,但它们的引用计数是独立的(因为它们是用同一个裸指针分别构造的,而不是通过复制或移动构造函数),所以每个 shared_ptr 在析构时都会认为自己是最后一个指向该内存的 shared_ptr,并尝试删除它。这就会导致未定义行为,通常是程序崩溃。
正确做法:shared_ptr<A> ptr1 = make_shared<A>(); shared_ptr<A> ptr2 = ptr1; // 现在 ptr2 是 ptr1 的一个副本,它们共享同一个对象和控制块
-
不要用shared_ptr管理不是new分配的内存,否则会导致内存泄漏的问题。
三、独占智能指针 unique_ptr
有一个简单类:
class A
{
private:
int _a;
public:
A()
{
cout << "A()" << endl;
}
A(const int a):_a(a)
{
cout << "A(int)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
1、使用方法
-
方法一:使用new关键字,分配内存并初始化
unique_ptr <类名> 指针名(new 类名(参数));
unique_ptr<A> p1(new A(10));
-
方法二:使用make_unique函数,C++14标准
unique_ptr <类名> 指针名 = make_unique<类名>(参数);
unique_ptr<A> p2 = make_unique<A>(10);
-
方法三:用已存在的地址(
此方法可能出错,易错
)类名 *p = new 类名(参数);
unique_ptr <类名> 指针名(p);
A *p = new A(10); unique_ptr<A> p3(p);
2、注意
-
不支持拷贝构造函数和赋值操作符,因为unique_ptr独占所指向的对象。
A *p = new A(10); unique_ptr<A> p1 = p; //错误,不支持赋值操作; unique_ptr<A> p2 = new A(10); //错误,不能将普通指针赋值给智能指针 unique_ptr<A> p3(new A(10)); unique_ptr<A> p4 = p3; //错误,不支持拷贝构造函数; unique_ptr<A> p5; p5 = p3; //错误,不支持赋值操作;
-
不支持与shared_ptr相互转换,
因为shared_ptr允许多个指针指向同一个对象,而unique_ptr独占所指向的对象。 -
不要用同一个指针初始化多个unique_ptr,否则会报错。
shared_ptr<A> ptr1 = make_shared<A>(10); shared_ptr<A> ptr2 = ptr1; // 现在 ptr1 和 ptr2 共享同一个 A 对象 cout << "ptr1: " << *ptr1 << endl; cout << "ptr2: " << *ptr2 << endl; // 当 ptr1 和 ptr2 都超出作用域时,A 对象才会被删除
-
不要用unique_ptr管理不是new分配的内存,否则会报错。
-
get方法返回裸指针,显示原始指针的地址。
A *p = new A(10); unique_ptr<A> up(p); // 注意:这里不需要 p,因为 unique_ptr 会自己管理内存。 // 更好的做法是直接使用 make_unique:unique_ptr<A> up = make_unique<A>(10); cout << "裸指针的地址:" << p << endl; // 注意:此时 p 已经是悬空指针,不建议使用! // 由于 up 接管了 p 的内存,p 不再有效。上面的输出仅用于说明,实际应避免。 cout << "智能指针的地址:" << up << endl; cout << "up.get():" << up.get() << endl; cout << "up的地址:" << &up << endl; // 不需要 delete p,因为 unique_ptr 会自动处理。 return 0;
3、作为参数传递
当unique_ptr作为参数传递给函数时,应该传递unique_ptr的引用,不能进行值传递,因为unique_ptr不支持拷贝构造函数和赋值操作符。
class A
{
public:
A()
{
cout << "A()" << endl;
}
int _a;
};
void fun(unique_ptr<A> p)
{
cout << p->_a << endl;
}
int main()
{
unique_ptr<A> p(new A(10));
fun(p);
return 0;
}
unique_ptr 被设计为独占的智能指针。一旦 unique_ptr 拥有某个资源(在这里是一个 A 类型的对象),
它就不允许其他 unique_ptr 或任何形式的复制来共享这个资源。
这是通过删除 unique_ptr 的拷贝构造函数和赋值操作符来实现的。
当unique_ptr< A > 作为值参数传递给函数 fun 时,
编译器会尝试调用被删除的复制构造函数来创建一个新的 unique_ptr< A > 实例作为函数参数。
这就是为什么会看到一个编译错误,指出正在使用一个被删除的函数。
四、weak_ptr
五、智能指针删除器
在默认情况下,智能指针要过期时,会自动调用delete来释放内存。
但有时,我们可能需要使用其他方式来释放内存,比如调用自定义的析构函数或者使用其他类型的内存管理器。
这时,我们可以使用智能指针的删除器来实现。
删除器可以是全局函数
、lambda表达式
或者仿函数
。智能指针在过期时会调用删除器来释放内存。
1、shared_ptr的删除器
#include<iostream>
#include<memory>
using namespace std;
class A
{
public:
A()
{
std::cout << "A()" << std::endl;
}
~A()
{
std::cout << "~A()" << std::endl;
}
A(const int a):_a(a)
{
cout << "a = " << a << endl;
}
int _a;
};
void deletefunc(A *a)
{
cout << "普通函数删除器" << endl;
//此处可以不delete a,但是后果自负,不会调用析构函数
delete a;
}
struct deleteclass
{
void operator()(A *a)
{
cout << "仿函数删除器" << endl;
delete a;
}
};
auto deletelamb = [](A *a)
{
cout << "lambda表达式删除器" << endl;
delete a;
};
int main()
{
//shared_ptr<A> p1(new A(10)); //使用缺省的删除器
//普通函数做删除器
shared_ptr<A> p2(new A(20), deletefunc);
//仿函数删除器
shared_ptr<A> p3(new A(30), deleteclass());
//lambda表达式做删除器
shared_ptr<A> p4(new A(40), deletelamb);
return 0;
}
2、unique_ptr的删除器
unique_ptr的删除器
unique_ptr<A,decltype(deletefunc)*> p5(new A(10), deletefunc);
unique_ptr<A, deleteclass> p6(new A(20), deleteclass());
unique_ptr<A, decltype(deletelamb)> p7(new A(30), deletelamb);
六、weak_ptr
1、shared_ptr存在的问题
shared_ptr内部维护了一个引用计数,当引用计数为0时,shared_ptr会自动释放所管理的对象。
当出现循环引用时,引用计数永远不会为0,导致内存泄漏。
#include<iostream>
#include<memory>
using namespace std;
class B;
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
shared_ptr<B> pb;
};
class B
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" << endl;
}
shared_ptr<A> pa;
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
//不会调用析构函数
pa->pb = pb;
pb->pa = pa;
cout << "pa.use_count() = " << pa.use_count() << endl; // 输出 2
}
2、weak_ptr的引入
weak_ptr是为了配合shared_ptr引入的,它指向一个由shared_ptr管理的资源但不影响资源的生命周期。weak_ptr不会增加引用计数,因此不会阻止shared_ptr释放所管理的对象。
weak_ptr可以用来解决循环引用的问题,它可以与shared_ptr相互转换,但是不能直接访问所管理的对象,需要通过shared_ptr来访问。
//其他均不变,将A、B类中的shared_ptr改为weak_ptr
weak_ptr<B> pb;
weak_ptr<A> pa;
3、weak_ptr的使用
weak_ptr不控制对象的生命周期,但是它知道对象是否或者。
weak_ptr没有重载*和->运算符,因此不能直接访问所管理的对象。
weak_ptr的成员函数
-
operator=();//将shared_ptr或weak_ptr赋值给weak_ptr
-
expired(); //判断weak_ptr所管理的对象是否已经释放,返回true表示已经释放,返回false表示未释放
-
lock(); //将weak_ptr转换为shared_ptr,如果weak_ptr所管理的对象已经释放,则返回一个空的shared_ptr,这个行为是线程安全的。
-
reset(); //将weak_ptr重置为空
-
swap(); //交换两个weak_ptr所管理的对象
错误方式:
if(pa->pb.expired()) == true
cout << "pa->pb已经释放" << endl;
else
cout << "pa->pb.lock() << pa->pb.lock()->_a" << endl;
正确方式:
shared_ptr<B> temp = pa->pb.lock();
//将weak_ptr转换为shared_ptr。
if(temp == nullptr)
cout << "pa->pb已经释放" << endl;
else
cout << "pa->pb.lock() << temp->_a" << endl;