普通指针创建动态内存的问题:
1.new和new[]的内存需要使用delete和delete []释放。
2.有时忘记释放内存。
3.不知该在何时释放内存。
智能指针的优点:
在不需要对象时自动释放对象,从而避免内存泄漏和其他与内存管理相关的问题。
智能指针有:unique_ptr,shared_ptr和weak_ptr
C++中几种常见的智能指针
std::unique_ptr
独占所有权:unique_ptr 拥有其所指向对象的独占所有权。同一时间内只能有一个 unique_ptr 指向某个对象。
移动语义:当 unique_ptr 被赋值或移动到另一个 unique_ptr 时,原 unique_ptr 会失去对对象的所有权,而新 unique_ptr 会获得所有权。
自定义删除器:可以为 unique_ptr 指定一个自定义的删除器,以在删除对象时执行特定的操作。
不支持复制操作:由于 unique_ptr 拥有独占所有权,因此它不支持复制构造函数和复制赋值操作符。
std::shared_ptr
共享所有权:多个 shared_ptr 可以指向同一个对象,并共享其所有权。当最后一个拥有该对象的 shared_ptr 被销毁或重置时,对象才会被删除。
引用计数:shared_ptr 使用引用计数来跟踪指向某个对象的 shared_ptr 的数量。当引用计数变为0时,对象会被自动删除。
循环引用问题:由于 shared_ptr 的共享所有权特性,如果不当使用可能会导致循环引用问题,从而阻止对象被正确删除。为了解决这个问题,可以使用 std::weak_ptr。
std::weak_ptr
弱引用:weak_ptr 是对 shared_ptr 所管理对象的弱引用,它不增加对象的引用计数。因此,它不会阻止对象被 shared_ptr 删除。
解决循环引用:weak_ptr 通常用于解决由 shared_ptr 引起的循环引用问题。通过使用 weak_ptr 代替其中一个 shared_ptr,可以确保在循环引用中至少有一个指针不会增加引用计数。
访问原始指针:虽然 weak_ptr 本身不拥有对象,但它可以安全地访问对象的原始指针(通过调用 lock() 方法)。如果对象仍然存在,lock() 将返回一个指向该对象的 shared_ptr;否则,将返回一个空的 shared_ptr。
以下是一些可能导致内存泄漏的情况:
循环引用:在使用std::shared_ptr时,如果两个或更多的智能指针相互持有对方的引用(形成循环),则它们将永远不会被释放,从而导致内存泄漏。为了避免这种情况,可以使用std::weak_ptr来打破循环。
自定义删除器:如果你为智能指针提供了一个自定义删除器,并且这个删除器存在缺陷,那么可能会导致内存泄漏。例如,删除器可能只是简单地调用delete,但如果你的对象需要特殊的清理逻辑(如关闭文件句柄或释放其他资源),那么这些资源可能不会被正确释放。
unique_ptr
unique_ptr 独占智能指针。
它独占指向的对象,也就是说,某个时刻只有一个 unique_ptr 指向一个给定对象,当unique_ptr 被销毁时,它所指向的对象也被销毁。
需要包含头文件: #include
class Student
{
public:
string m_name;//学生姓名
Student(const string& name = "") :m_name(name)//构造函数
{
cout << "构造函数:" << m_name << ",被创建" << endl;
}
~Student()
{
cout << "析构函数:" << m_name << ",被释放了" << endl;
}
};
int main()
{
auto p1 = new Student("刘备");//利用new创建一个新对象
//delete p1; //忘记释放对象,出现内存泄漏
return 0;
}
类定义
下面是unique_ptr的部分源码.
template< class T, class Del = default_delete<T> >
class unique_ptr {
public:
unique_ptr( ); //默认构造函数
explicit unique_ptr( pointer Ptr ); //构造函数,不允许隐式转换
unique_ptr (pointer Ptr, typename remove_reference<Del>::type&& Deleter);//提供delete 方法
unique_ptr( const unique_ptr& Right) = delete; //不允许拷贝构造
unique_ptr& operator=(const unique_ptr& Right ) = delete;//不允许赋值 =
};
说明:unique_ptr是"唯一"类型的智能指针,不允许多个智能指针指向同一个对象。因此不支持 = 和 拷贝构造。
初始化
//方法一:
unique_ptr<Student> p1(new Student("刘备"));// 分配内存并初始化。
//方法二:
unique_ptr<Student> p2 = make_unique<Student>("曹操"); // C++14标准提供,优先使用
//方法三:
Student* ps = new Student("孙权");
unique_ptr<Student> p3(ps);// 用已存在的地址初始化。
使用智能指针
智能指针和普通指针访问对象成员的方式是一样的,通过->访问成员。例如
class Student
{
public:
Student(const string& name = ""):m_name(name)//构造函数
{
cout << "构造函数:" << m_name << ",被创建" << endl;
}
~Student()
{
cout << "析构函数:" << m_name << ",被释放了" << endl;
}
void show()const
{
cout << "学生名字:" << m_name << endl;
}
private:
string m_name;//学生姓名
};
int main()
{
Student* p1 = new Student("刘备");
unique_ptr<Student> p2(new Student("曹操"));
p1->show(); //访问p1的成员函数
p2->show();//访问p2的成员函数
return 0;
}
作为函数参数(经常使用)
传引用。不能传值(因为unique_ptr没有拷贝构造函数)
void test(const unique_ptr<Student>& p)
{
cout << "test函数" << endl;
p->show();
}
int main()
{
unique_ptr<Student> p1(new Student("曹操"));
test(p1);
return 0;
}
添加删除器(面试可能问)
可以通过函数,仿函数,lambda表达式,自定义删除器。
//自定义删除器,普通函数
void DeleteFunc(Student* ps)
{
cout << "自定义删除器(普通函数)" << endl;
delete ps;//如果漏写这句就可能出现内存泄漏,但这是你的代码问题,不是智能指针问题
}
//自定义删除器,仿函数
class DeleteClass
{
public:
void operator()(Student* ps) //重载()
{
cout << "自定义删除器(仿函数)" << endl;
delete ps;
}
};
//自定义删除器,lambda
auto DeleteLambda = [](Student* ps)
{
cout << "自定义删除器(lambda)" << endl;
delete ps;
};
int main()
{
//自定义删除器,普通函数
unique_ptr<Student,decltype(DeleteFunc)*> p1(new Student("刘备"), DeleteFunc);
//自定义删除器,仿函数
unique_ptr<Student, DeleteClass> p2(new Student("曹操"), DeleteClass());
//自定义删除器,lambda
unique_ptr<Student, decltype(DeleteLambda)> p3(new Student("孙权"), DeleteLambda);
return 0;
}
shared_ptr
shared_ptr共享智能指针,多个shared_ptr可以指向相同的对象,在内部有一个关联的计数器。通常称其为引用计数。
**引用计数是一种内存管理技术,用于跟踪共享资源的引用情况,并在引用为0时进行释放。引用计数使用一个计数器来统计当前指针指向的对象被多少类的对象所使用,即记录指针指向对象被引用的次数。**当新的shared_ptr与对象关联时,引用计数加1。当shared_ptr结束时,引用计数减1。当引用计数变为0时,表示没有任何shared_ptr与对象关联,则释放该对象。
2.1 类定义
初始化
//方法一:
shared_ptr<Student> p1(new Student("孙悟空"));// 分配内存并初始化。
//方法二:
shared_ptr<Student> p2 = make_unique<Student>("猪八戒"); // C++14标准,推荐使用
//方法三:
Student* ps = new Student("沙僧");
shared_ptr<Student> p3(ps);// 用已存在的地址初始化。
//方法四:
shared_ptr<Student> p4(p3); //利用p3初始化p4
//方法五:
shared_ptr<Student> p5 = p3; //利用p3初始化p5
详细代码如下:
详细代码如下
#include <iostream>
using namespace std;
class Student
{
public:
Student(const string& name = ""):m_name(name)//构造函数
{
cout << "构造函数:" << m_name << ",被创建" << endl;
}
~Student()
{
cout << "析构函数:" << m_name << ",被释放了" << endl;
}
void show()const
{
cout << "学生名字:" << m_name << endl;
}
private:
string m_name;//学生姓名
};
int main()
{
//方法一:
shared_ptr<Student> p1(new Student("孙悟空"));// 分配内存并初始化。
//方法二:
shared_ptr<Student> p2 = make_unique<Student>("猪八戒"); // C++14标准,推荐使用
//方法三:
Student* ps = new Student("沙僧");
shared_ptr<Student> p3(ps);// 用已存在的地址初始化。
//方法四:
shared_ptr<Student> p4(p3); //利用p3初始化p4
//方法五:
shared_ptr<Student> p5 = p3; //利用p3初始化p5
cout << "孙悟空的引用计数:"<< p1.use_count() << endl;
cout << "猪八戒的引用计数:" << p2.use_count() << endl;
cout << "沙僧的引用计数:" << p3.use_count() << endl;
return 0;
}
作为函数参数
传值和传引用都可以,建议传引用。
void test01(shared_ptr<Student> p)//传值
{
p->show();
}
void test02(shared_ptr<Student>& p) //传引用
{
p->show();
}
int main()
{
shared_ptr<Student>p = make_shared<Student>("菩提老祖");
test01(p);
test02(p);
return 0;
}
添加删除器
//自定义删除器,普通函数
void DeleteFunc(Student* ps)
{
cout << "自定义删除器(普通函数)" << endl;
delete ps;
}
//自定义删除器,仿函数
class DeleteClass
{
public:
void operator()(Student* ps) //重载()
{
cout << "自定义删除器(仿函数)" << endl;
delete ps;
}
};
auto DeleteLambda = [](Student* ps)
{
cout << "自定义删除器(lambda)" << endl;
delete ps;
};
int main()
{
shared_ptr<Student> p1(new Student("孙悟空"), DeleteFunc);
shared_ptr<Student> p2(new Student("猪八戒"), DeleteClass());
shared_ptr<Student> p3(new Student("沙僧"), DeleteLambda);
return 0;
}
shared_ptr,weak_ptr
shared_ptr之间的循环引用问题
在C++中,shared_ptr是用来管理动态分配对象的生命周期的智能指针,它使用引用计数来确保当最后一个指向对象的shared_ptr被销毁时,对象本身也会被销毁。然而,当shared_ptr之间形成循环引用时,这个问题就变得复杂了。
循环引用发生在两个或多个shared_ptr相互引用对方所管理的对象时。由于每个shared_ptr都持有一个引用计数,只要引用计数不为零,它所指向的对象就不会被销毁。因此,即使程序的其他部分不再需要这些对象,它们也不会被释放,从而导致内存泄漏。
例如:
class A;
class B;
class A {
public:
A(){
m_a = new int[10];//动态创建10个元素
cout << "A()" << endl;
}
~A() {
delete[]m_a;//释放内存
cout << "~A()" << endl;
}
shared_ptr<B> b_ptr;
private:
int* m_a;//模拟动态创建的内存
};
class B {
public:
shared_ptr<A> a_ptr;
B() {
m_b = new int[10];//动态创建10个元素
cout << "B()" << endl;
}
~B() {
delete[]m_b;//释放内存
cout << "~B()" << endl;
}
private:
int* m_b;
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// a 和 b 的引用计数都是 2,因为它们相互引用
// 当 a 和 b 离开作用域时,它们的引用计数不会降到 0
// 因此,A 和 B 的实例不会被销毁,导致内存泄漏
return 0;
}
weak_ptr的作用
为了解决shared_ptr之间的循环引用问题,C++标准库提供了weak_ptr。weak_ptr是对shared_ptr所管理对象的一种弱引用,它不会增加对象的引用计数。因此,当最后一个shared_ptr被销毁时,即使还有weak_ptr指向该对象,对象也会被销毁。
修改上面的例子,使用weak_ptr来打破循环引用
class A;
class B;
class A {
public:
A(){
m_a = new int[10];//动态创建10个元素
cout << "A()" << endl;
}
~A() {
delete[]m_a;//释放内存
cout << "~A()" << endl;
}
weak_ptr<B> b_ptr;
private:
int* m_a;//模拟动态创建的内存
};
class B {
public:
shared_ptr<A> a_ptr;
B() {
m_b = new int[10];//动态创建10个元素
cout << "B()" << endl;
}
~B() {
delete[]m_b;//释放内存
cout << "~B()" << endl;
}
private:
int* m_b;
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// a 和 b 的引用计数都是 2,因为它们相互引用
// 当 a 和 b 离开作用域时,它们的引用计数不会降到 0
// 因此,A 和 B 的实例不会被销毁,导致内存泄漏
return 0;
}
使用weak_ptr时需要小心,因为它不保证所指向的对象仍然存在。在访问weak_ptr所指向的对象之前,通常通过调用lock函数测试其有效性,并将其升级为shared_ptr。如果lock()返回空指针,则意味着原始对象已经被销毁。
本篇完!