C++新特性17_智能指针的原理
1. 什么是智能指针?
- (1)用起来像指针
- (2) 会自己对资源进行释放
以下将会对智能指针的原理逐步进行演示。
2. 手动进行资源的释放形式
在没有智能指针之前,我们都是采用手动释放的方法。
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
//智能指针
//1.用起来像指针
//2.会自己对资源进行释放
class CStudent
{
public:
CStudent()
{
}
void test()
{
cout << "CStudent" << endl;
}
private:
char* m_pszBuf;
int m_nSex;
};
int main(int argc, char* argv[])
{
//对象为无参构造
CStudent* pStu = new CStudent();
//手动进行释放
if (pStu!=nullptr)
{
delete pStu;
pStu = nullptr;
}
return 0;
}
3. 智能指针雏形:利用类的构造析构实现资源管理
创建一个类,利用该类的构造和析构(进出作用域自动被编译器调用)的机制,来解决资源自动释放的问题。
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
//智能指针
//1.用起来像指针
//2.会自己对资源进行释放
class CStudent
{
public:
CStudent()
{
}
void test()
{
cout << "CStudent" << endl;
}
private:
char* m_pszBuf;
int m_nSex;
};
//创建一个类,利用该类的构造和析构(进出作用域自动被编译器调用)的机制
//来解决资源自动释放的问题
//智能指针雏形 用于管理资源
class CSmartPtr
{
public:
//一定要是一个堆对象,因为是利用构造析构管理资源
CSmartPtr(CStudent* pObj) {
m_pObj = pObj;
}
~CSmartPtr() {
if (m_pObj != nullptr)
{
delete m_pObj;
}
}
private:
CStudent* m_pObj;//将资源放入智能指针类中,管理起来
};
int main(int argc, char* argv[])
{
//这里可以完成资源的自动释放,但是用起来不像是一个指针
CSmartPtr sp(new CStudent());
return 0;
}
4. 需要想办法让对象用起来像是一个指针
前面,我们学会了如何使用引用计数及写时拷贝,这是理解智能指针必不可少的方法。但是,在实际写代码中,我们其实更倾向于让程序员对于资源的管理没有任何的感知,也就是说,最好让程序员只需要考虑资源的何时申请,对于何时释放以及资源内部如何计数等问题,统统交给编译器内部自己处理。
4.1 “->”,"*"取内容,"bool"的运算符重载
智能指针另外一点就是在使用上要像真正的指针一样可以支持取内容, 指针访问成员->等操作,因此,就需要对这些运算符进行重载。
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
//智能指针
//1.用起来像指针
//2.会自己对资源进行释放
class CStudent
{
public:
CStudent()
{
}
void test()
{
cout << "CStudent" << endl;
}
private:
char* m_pszBuf;
int m_nSex;
};
//创建一个类,利用该类的构造和析构(进出作用域自动被编译器调用)的机制
//来解决资源自动释放的问题
//智能指针雏形 用于管理资源
class CSmartPtr
{
public:
//一定要是一个堆对象,因为是利用构造析构管理资源
CSmartPtr(CStudent* pObj) {
m_pObj = pObj;
}
~CSmartPtr() {
if (m_pObj != nullptr)
{
delete m_pObj;
}
}
//需要想办法让对象用起来像是一个指针
//为了使sp像一个指针的写法
//将->进行运算符重载,返回内部的类的指针,指针类型为CStudent
//这样的话sp可以利用返回的指针指向CStudent对象,并指向其内部成员函数,写法:
//CSmartPtr sp(new CStudent()); sp->test();
CStudent* operator->() {
return m_pObj;
}
//重载*,返回一个CStudent的引用,使得(*sp).test();取内容成立
CStudent& operator*() {
return *m_pObj;
}
//重载bool类型, if (sp) {}
operator bool() {
return m_pObj != nullptr;
}
//&取地址也是可以的
private:
CStudent* m_pObj;//将资源放入智能指针类中,管理起来
};
int main(int argc, char* argv[])
{
//默认浅拷贝,堆空间会重复析构,程序会崩溃掉
CSmartPtr sp(new CStudent());
CSmartPtr sp2=sp;
return 0;
}
但是上述在碰到指针赋值等操作时默认浅拷贝,堆空间会重复析构,程序会崩溃掉
,这个怎么解决呢?以下是解决问题的两种思路
4.1.1 不允许=号运算符重载和拷贝构造(不太建议,简单粗暴)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
//智能指针
//1.用起来像指针
//2.会自己对资源进行释放
class CStudent
{
public:
CStudent()
{
}
void test()
{
cout << "CStudent" << endl;
}
private:
char* m_pszBuf;
int m_nSex;
};
//创建一个类,利用该类的构造和析构(进出作用域自动被编译器调用)的机制
//来解决资源自动释放的问题
//智能指针雏形 用于管理资源
class CSmartPtr
{
public:
//一定要是一个堆对象,因为是利用构造析构管理资源
CSmartPtr(CStudent* pObj) {
m_pObj = pObj;
}
~CSmartPtr() {
if (m_pObj != nullptr)
{
delete m_pObj;
}
}
//禁止=号运算符重载
CSmartPtr& operator=(CSmartPtr&) = delete;
//禁止拷贝
CSmartPtr(CSmartPtr&) = delete;
//需要想办法让对象用起来像是一个指针
//为了使sp像一个指针的写法
//将->进行运算符重载,返回内部的类的指针,指针类型为CStudent
//这样的话sp可以利用返回的指针指向CStudent对象,并指向其内部成员函数,写法:
//CSmartPtr sp(new CStudent()); sp->test();
CStudent* operator->() {
return m_pObj;
}
//重载*,返回一个CStudent的引用,使得(*sp).test();成立
CStudent& operator*() {
return *m_pObj;
}
//重载bool类型, if (sp) {}
operator bool() {
return m_pObj != nullptr;
}
//&取地址也是可以的
private:
CStudent* m_pObj;//将资源放入智能指针类中,管理起来
};
int main(int argc, char* argv[])
{
//放入块作用域进行调试
{
//这里可以完成资源的自动释放,是将对象放入对象中
CSmartPtr sp(new CStudent());
CSmartPtr sp3(new CStudent());
//=号的运算符重载,会造成崩溃,防止崩溃
//防崩溃写法:CSmartPtr& operator=(CSmartPtr&) = delete;
//sp3 = sp;
//指针是可以进行赋值的,但是此处运行后会崩溃
//防崩溃写法:CSmartPtr(CSmartPtr&) = delete;
//CSmartPtr sp2 = sp;//拷贝构造,默认为浅拷贝
}
//解决上面拷贝构造引起的崩溃,方法:
//1.不允许=号运算符重载,拷贝构造(不太建议)
//2.使用拷贝移动的语法(防止资源多次释放)
return 0;
}
正常情况下,不允许=号运算符重载和拷贝构造这是不合理的。
可以使用拷贝移动的写法
4.1.2 使用拷贝移动的语法(防止资源多次释放,但是可能造成原来的对象丢失,移动构造中被删除了)
将对象1的指针赋给对象2,但是对象1的就会丢失 auto_ptr 98中的,缺陷明显
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
//智能指针
//1.用起来像指针
//2.会自己对资源进行释放
class CStudent
{
public:
CStudent()
{
}
void test()
{
cout << "CStudent" << endl;
}
private:
char* m_pszBuf;
int m_nSex;
};
//创建一个类,利用该类的构造和析构(进出作用域自动被编译器调用)的机制
//来解决资源自动释放的问题
//智能指针雏形 用于管理资源
class CSmartPtr
{
public:
//一定要是一个堆对象,因为是利用构造析构管理资源
CSmartPtr(CStudent* pObj) {
m_pObj = pObj;
}
~CSmartPtr() {
if (m_pObj != nullptr)
{
delete m_pObj;
}
}
//禁止=号运算符重载
//CSmartPtr& operator=(CSmartPtr&) = delete;
//使用拷贝移动的语法(防止资源多次释放)
CSmartPtr& operator=(CSmartPtr& sp) {
//m_pObj不为空就删除内容
if (m_pObj!=nullptr)
{
delete m_pObj;
}
m_pObj = sp.m_pObj;//赋值
sp.m_pObj = nullptr;//原有的就不需要了
return *this;
}
//禁止拷贝构造
CSmartPtr(CSmartPtr&) = delete;
//需要想办法让对象用起来像是一个指针
//为了使sp像一个指针的写法
//将->进行运算符重载,返回内部的类的指针,指针类型为CStudent
//这样的话sp可以利用返回的指针指向CStudent对象,并指向其内部成员函数,写法:
//CSmartPtr sp(new CStudent()); sp->test();
CStudent* operator->() {
return m_pObj;
}
//重载*,返回一个CStudent的引用,使得(*sp).test();成立
CStudent& operator*() {
return *m_pObj;
}
//重载bool类型, if (sp) {}
operator bool() {
return m_pObj != nullptr;
}
//&取地址也是可以的
private:
CStudent* m_pObj;//将资源放入智能指针类中,管理起来
};
int main(int argc, char* argv[])
{
//放入块作用域进行调试
{
//这里可以完成资源的自动释放,是将对象放入对象中
CSmartPtr sp(new CStudent());
CSmartPtr sp3(new CStudent());
//=号的运算符重载,会造成崩溃,防止崩溃
//防崩溃写法:CSmartPtr& operator=(CSmartPtr&) = delete;
sp3 = sp;
//指针是可以进行赋值的,但是此处运行后会崩溃
//防崩溃写法:CSmartPtr(CSmartPtr&) = delete;
//CSmartPtr sp2 = sp;//拷贝构造,默认为浅拷贝
}
//解决上面重复析构引起的崩溃,方法:
//1.不允许=号运算符重载,拷贝构造(不太建议)
//2.使用拷贝移动的语法(防止资源多次释放,但是可能造成原来的sp丢失,移动构造中被删除了)
//将对象1的指针赋给对象2,但是对象1的就会丢失 auto_ptr 98中的,缺陷明显
//3.结合前面的引用计数及写时拷贝
return 0;
}
4.1.3 推荐的解法:结合前面两篇的引用计数及写时拷贝,新的智能指针的写法
且听下篇分解!
5. 智能指针的简要形式代码分析
以下是对智能指针内部实现原理进行简要分析及实现,涉及了指针运算符重载及编译器的优化。(来自C++全)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
cout << "Person的有参构造调用" << endl;
this->m_Age = age;
}
void showAge()
{
cout << "年龄为: "<< this->m_Age << endl;
}
~Person()
{
cout << "Person的析构调用" << endl;
}
int m_Age;
};
class SmartPoint
{
public:
SmartPoint(Person * person)
{
this->m_person = person;
}
//重载->运算符,使得sp->showAge()在编译器优化之后直接就可以调用Person的方法
Person * operator->()
{
return this->m_person;
}
//重载 * 运算符
Person& operator*()
{
return *m_person;
}
~SmartPoint()
{
if (this->m_person)
{
delete this->m_person;
this->m_person = NULL;
}
}
private:
Person * m_person;
};
void test01()
{
//Person * p = new Person(18);
//(*p).showAge();
//p->showAge();
//delete p;
//利用智能指针(对象是在栈上,出作用域自动析构) 管理 new出来的person的释放操作
SmartPoint sp(new Person(18));
sp->showAge(); // 本质sp->->showAge(); 编译器简化为 sp->showAge();
(*sp).showAge();
}
int main(){
test01();
system("pause");
return EXIT_SUCCESS;
}