智能指针是一个类对象。在对象生命周期结束,将会被delete(栈区对象)。不仅对象会被删除,它指向的内存也会被自动delete,以免造成内存泄漏。
首先来看一个错误实例:
int main()
{
#ifdef _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
vector<Student*> arr;
for (int i = 0; i < 10; i++)
{
Student *p = new Student(i);
arr.push_back(p);
}
cin.get();
return 0;
}
class AutoDestoryStudent
{
public:
AutoDestoryStudent()
{
m_p = NULL;
}
~AutoDestoryStudent()
{
if (m_p)
{
delete m_p;
m_p = NULL;
}
}
Student* CreateObj(int nID)
{
return m_p = new Student(nID);
}
private:
Student* m_p;
};
int main()
{
#ifdef _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
vector<Student*> arr;
for (int i = 0; i < 10; i++)
{
AutoDestoryStudent obj;
arr.push_back(obj.CreateObj(i));
}
cin.get();
return 0;
}
1、shared_ptr。
shared_ptr可以让多个对象托管同一个指针,n个shared_ptr对象托管同一指针p。当指针p的Count为0时,该指针会被delete。shared_ptr对象消亡或托管了新的指针,都会导致原托管指针的Count–。
Tip1: shared_ptr所有权是可以被安全共享的,也就是说支持拷贝赋值,允许被多个“人”同时持有,就像原始指针一样。
Tip2: shared_ptr 具有完整的“值语义”(即可以拷贝赋值),所以,它可以在任何场合替代原始指针,而不用再担心资源回收的问题,比如用于容器存储指针、用于函数安全返回动态创建的对象
包含头文件:
#include
#include
#include
#include
using namespace std;
构建类Student:
class Student
{
public:
Student(int n) :m_nID(n)
{
};
~Student()
{
cout << m_nID << "\t~析构~\n";
}
int GetID()
{
return m_nID;
}
private:
int m_nID;
};
int main()
{
//1.0 多个shared_ptr可以共同管理同一个对象。
shared_ptr<Student> sp1 = make_shared<Student>(2);//Student(2)由sp1托管,
shared_ptr<Student> sp2 = sp1; //Student(2)同时交由sp2托管
shared_ptr<Student> sp3 = sp2; //Student(2)同时交由sp3托管
cout << sp1->GetID() << "\t"
<< sp2->GetID() << "\t"
<< sp3->GetID() << "\n\n";
//1.1 get()方法可以返回托管的指针。
Student * p = sp3.get(); // get返回托管的指针,p 指向 Student(2)
cout << p->GetID() << endl; //输出 2
//1.2 reset()方法托管新的指针。
sp1.reset(new Student(3)); // reset导致托管新的指针, 此时sp1托管Student(3)
cout << sp1->GetID() << endl; //输出 3
sp2.reset(new Student(4)); // sp2托管Student(4)
sp3.reset(new Student(5)); // sp3托管Student(5),Student(2)无人托管,被delete
cout << "end" << endl;
cin.get();
return 0;
}
const int nSize = 10;
vector<shared_ptr<Student>> arr;
for (int i = 0; i < nSize; i++)
{
shared_ptr<Student> sp = make_shared<Student>(i);
arr.push_back(sp);
}
Tip3: shared_ptr 的引用计数也导致了一个新的问题,就是“循环引用”,这在把 shared_ptr 作为类成员的时候最容易出现,典型的例子就是链表节点。
分析:两个节点指针刚创建时,引用计数都是 1。但指针互指(即拷贝赋值)之后,引用计数都变成了 2。shared_ptr 就无法意识不到这是一个循环引用,多算了一次计数,后果就是引用计数无法减到 0,无法调用析构函数执行 delete,最终导致内存泄漏。
想要从根本上杜绝循环引用,必须要用到它的“小帮手”:weak_ptr。
2、weak_ptr。
//接着上文的例子,来看接下来的程序:
class Node final
{
public:
using this_type = Node;
// 注意这里,别名改用weak_ptr
using shared_type = std::weak_ptr<this_type>;
public:
shared_type next; // 因为用了别名,所以代码不需要改动
};
auto n1 = make_shared<Node>(); // 工厂函数创建智能指针
auto n2 = make_shared<Node>(); // 工厂函数创建智能指针
n1->next = n2; // 两个节点互指,形成了循环引用
n2->next = n1;
assert(n1.use_count() == 1); // 因为使用了weak_ptr,引用计数为1
assert(n2.use_count() == 1); // 打破循环引用,不会导致内存泄漏
if (!n1->next.expired()) { // 检查指针是否有效
auto ptr = n1->next.lock(); // lock()获取shared_ptr
assert(ptr == n2);
}
详细来讲, weak_ptr 是为配合协助shared_ptr工作而引入的一种智能指针,用来解决shared_ptr相互引用时的死锁问题。它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用Count的增加或减少。如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用Count。
C++允许weak_ptr和shared_ptr相互转化,shared_ptr可以直接赋值给weak_ptr,weak_ptr可通过调用lock()获得shared_ptr。
int main()
{
#ifdef _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
shared_ptr<Student> ptrShare6(new Student(666666));
shared_ptr<Student> ptrShare7 = ptrShare6;
weak_ptr<Student> ptrWeak = ptrShare6;
cout << ptrWeak.use_count() << endl;//2
shared_ptr<Student> ptrShare8 = ptrWeak.lock();//转化
cout << ptrShare7.use_count() << endl;
cout << ptrShare8.use_count() << endl;//3
ptrShare8.reset();
cout << ptrShare6.use_count() << endl;
ptrShare7.reset();
cout << ptrShare6.use_count() << endl;
ptrShare6.reset();
cout << ptrShare6.use_count() << endl;
cin.get();
return 0;
}
3、unique_ptr。
unique_ptr,唯一的,独一无二,只允许自己访问相关内存。
创建智能指针对象方法:使用工厂函数创建智能指针。
int main()
{
auto pS1= make_unique<int>(123); // 工厂函数创建智能指针
assert(pS1&& *pS1== 123);
cout << *pS1<< endl;
auto pS2= make_unique<string>("Test string"); // 工厂函数创建智能指针
assert(!pS2->empty());
cout << *pS2<< endl;;
cin.get();
return 0;
}
//自定义的工厂函数make_unique()
template<class T, class... Args> // 可变参数模板
std::unique_ptr<T> // 返回智能指针
my_make_unique(Args&&... args) // 可变参数模板的入口参数
{
return std::unique_ptr<T>( // 构造智能指针
new T(std::forward<Args>(args)...)); // 完美转发
}
int main()
{
auto pS3 = my_make_unique<string>("my_make_unique"); // 工厂函数创建智能指针
assert(!pS3->empty());
cout << *pS3 << endl;;
cin.get();
return 0;
}
int main()
{
#ifdef _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
/*
unique_ptr唯一的,独一无二.只允许自己访问相关内存
*/
//3.1 重置(reset)unique_ptr
unique_ptr<Student> ptrUnique(new Student(111111));
cout << "unique_ptr:\t" << ptrUnique->GetID() << endl;
//调用 unique_ptr 的 reset() 方法将删除与之绑定的 raw 指针,并且将 unique_ptr 对象置空:
ptrUnique.reset();//相当于析构。(delete ptrUnique; ptrUnique = nullptr;)
//3.2错误示例 unique_ptr 独一无二,不可拷贝,只能移动。我们不能通过拷贝构造函数或赋值操作拷贝unique_ptr对象:
//std::unique_ptr ptrUnique2 = ptrUnique; //error
//3.3 unique_ptr 对象的所有权.
/*
unique_ptr对象不能拷贝,但可以移动。移动,也就是转移所有权。
*/
unique_ptr<Student> ptrUnique2(new Student(222222));
unique_ptr<Student> ptrUnique3 = std::move(ptrUnique2);
if (ptrUnique2 == nullptr)
{
cout << "ptrUnique2 is empty.\n" << std::endl;//转移
}
if (ptrUnique3 != nullptr)
{
cout << "ptrUnique3 is not empty.\n" << std::endl;
}
//3.4 unique_ptr的release()函数可直接将绑定raw指针的所有权释放,该函数会将绑定的 raw 指针返回,
unique_ptr<Student> ptrUnique4(new Student(444444));
if (ptrUnique4 != nullptr)
{
cout << "None empty.\n" << endl;
}
Student *p5 = ptrUnique4.release();//unique_ptr4释放raw所有权,并且移交给p5
if (ptrUnique4 == nullptr)
{
cout << "Empty.\n" << endl;
}
if (p5)
{
cout << "P5 get.\n" << endl;
delete p5;
p5 = nullptr;
}
cin.get();
return 0;
}
小结:
1.如果指针是“独占”使用,就应该选择 unique_ptr,它为裸指针添加了很多限制,更加安全。
2.如果指针是“共享”使用,就应该选择 shared_ptr,它的功能非常完善,用法几乎与原始指针一样。
3.应当使用工厂函数 make_unique()、make_shared() 来创建智能指针,强制初始化,而且还能使用 auto 来简化声明。
4.shared_ptr 有少量的管理成本,也会引发一些难以排查的错误,所以不要过度使用。
参考并且推荐罗剑锋老师的C++实战笔记,干货,满满的干货!