智能指针
1 为什么 要用智能指针------防止内存泄露
什么是智能指针?
智能指针是一种思想:
RAII :利用对象生命周期来控制程序资源
优点: 1 不需要显式的释放资源
2 随对象生命周期结束释放资源
智能指针分类 :
std::auto_ptr
std::unique_ptr
std::shared_ptr
1 std::auto_ptr
这个智能指针有它的缺点
当发生对象拷贝 ,或者赋值时,他会悬空前者(即,释放掉了前者的资源)。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year =1900, int month =1, int day =1 )
:year_(year)
,month_(month)
,day_(day)
{ }
int year_;
int month_ ;
int day_;
};
template <class T>
class Auto_ptr
{
public:
Auto_ptr( T* ptr = NULL)
:ptr_(ptr)
{}
//auto_ptr :: 1 拷贝之后就会发生悬空,即被拷贝的对象内容消失,转移到了靠背后的对象中
Auto_ptr( Auto_ptr<T>& prev)
:ptr_(prev.ptr_)
{
prev.ptr_ = NULL;
}
~Auto_ptr()
{
if( ptr_ )
delete ptr_;
}
//auto_ptr 2 被赋值之后发生悬空,前面的对象悬空 ,即 this = tmp ; tmp 悬空 a = 2; 原有2中资源悬空
Auto_ptr<T>& operator =(Auto_ptr<T>& tmp)
{
// 检查是否自己给自己赋值
if(this == &tmp)
{
return *this;
}
else
{
if(! ptr_)
{
delete ptr_;//防止内存泄漏
}
//转移tmp 的资源到 this 中去
ptr_ = tmp.ptr_;
tmp.ptr_ = NULL; //悬空 赋值者的资源
return *this;//return *ptr_;
}
}
T& operator *()
{
return * ptr_;
}
T* operator ->()
{
return ptr_;
}
private:
T* ptr_;
};
int main()
{
Auto_ptr<Date> a(new Date);
Auto_ptr<Date> copy(a);
//预期 下面两种方式都会发生错误
// cout<<a->year_<<endl;
a->year_ = 2018;
return 0;
}
2std::unique_ptr
unique_ptr 独一无二的指针,
既然独一无二,当然不能进行拷贝,不能赋值拷贝,那么我们将构造函数和 拷贝构造函数都进行私有化,c++11 在函数后边 =delete 私有化
#include <iostream>
using namespace std;
//unique_ptr 独一无二的只能指针,简单粗暴 ,防止拷贝,防止赋值
class Date
{
public:
Date(int year , int month, int day)
:year_(year)
,month_(month)
,day_(day)
{}
int year_;
int month_;
int day_;
};
template <class T>
class Unique_ptr
{
public:
Unique_ptr(T* a = NULL)
:a_(a)
{}
~Unique_ptr()
{
if(a_)
delete a_;
}
T& operator*()
{
return *a_;
}
T* operator->()
{
return a_;
}
private:
//防止拷贝 和 赋值 ,我们将其定义为私有成员函数,并且只做声明,不实现
Unique_ptr(const Unique_ptr<T>& source);
Unique_ptr& operator = (Unique_ptr<T>& target);
//c++11 新玩法 ,在 函数后面添加 = delete
T* a_;
};
int main()
{
return 0;
}
3 std::shared_ptr
shared_ptr 采用引用计数的原理,
类似于,解决前拷贝的原理,当引用计数器大于0 ,即使用拷贝或赋值拷贝引用计数++;
由于++操作是非原子性的,所以要保证线程安全,我们进行加锁。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
//共享指针 ,原理 采用 浅拷贝 + 引用计数的思想
template <class T>
class Shared_ptr
{
public:
Shared_ptr(T* ptr = nullptr)
:ptr_(ptr)
,pMutex_(new mutex)
,pCount_(new int(1))
{}
~Shared_ptr()
{
Relese();
}
Shared_ptr(const Shared_ptr<T>& p)
:ptr_(p.ptr_)
,pMutex_(p.pMutex_)
,pCount_(p.pCount_)
{
AddpCount();//引用计数
}
Shared_ptr& operator = (Shared_ptr<T>& sp)
{
if(&sp != this )//防止给自己赋值
{
delete this->ptr_ ;//先释放自己的资源防止内存泄漏
//标注资源位置
this->ptr_ = sp.ptr_;
this->pCount_ = sp.pCount_;
this->pMutex_ = sp.pMutex_;
AddpCount();
}
return *this;
}
int Get()
{
return *pCount_;
}
T& operator *(){return *ptr_; }
T* operator ->(){return ptr_; }
private:
void AddpCount()
{
pMutex_->lock();
++(*pCount_);//这里的引用计数参数必须是指针类型, 因为我们只保留一份数据,传值的话就保留了多份数据,不符合引用计数的思想
pMutex_->unlock();
}
void Relese()
{
bool delete_falg = false;
pMutex_->lock();
--*pCount_;
if(*pCount_ == 0)
delete_falg = true;
pMutex_->unlock();
if(delete_falg == true)
{
delete pCount_;
delete pMutex_;
delete ptr_;
}
}
T* ptr_;
mutex* pMutex_;
//这里的引用计数参数必须是指针类型, 因为我们只保留一份数据,传值的话就保留了多份对象,不符合引用计数的思想
int* pCount_;
};
std::shared_ptr 的循环引用问题:
struct ListNode
{
//什么类型的对象,就需要什么类型的节点类型
shared_ptr<ListNode> prev_;//智能指针
shared_ptr<ListNode> next_;//智能指针
};
//即 一个ListNode 具有两个智能指针,
int main()
{
shared_ptr<ListNode> node1(new ListNode)
shared_ptr<ListNode> node2(new ListNode)
node1 ->next_ = node2;
node2 ->prev_ = node1;
return 0;
}
//注意 ;node1 想要释放,就需要node2不使用node1
;
node2 想释放,就需要node1 不使用,
这样形成了循环引用,
导致 node1 中的引用计数一直是2;
node2 中的引用计数一直是2;
解决方案 ,我们将节点内的指针的类型改为weak_ptr;
注意weak_ptr 是一个专门辅助shard_ptr的;它不会增加引用计数:
struct ListNode
{
//weak_ptr 不会增加引用计数,专门辅助shared_ptr
weak_ptr<ListNode> prev_;//智能指针
weak_ptr<ListNode> next_;//智能指针
};
//即 一个ListNode 具有两个智能指针,
int main()
{
shared_ptr<ListNode> node1(new ListNode)
shared_ptr<ListNode> node2(new ListNode)
node1 ->next_ = node2;
node2 ->prev_ = node1;
return 0;
}
C++ 强制类型转换:
1 为什么C++又有自己的强制类型转换
c中的类型转换可视性差,不容易快速定位错误位置,c++为了类型转换的可视性,c++增加了4种强制类型转换。
1 static_cast
static_cast 用于静态类型转换(不能用于多态等),并且,相互转换的类型得有所联系。
2 reinterpret_cast 为操作数的位模式提供低层次的重新解释
reinterpret_cast用于不同类型之间的强制转换。
3 const_cast 用于修改const类型变量。
4 dynamic_cast
1 dynamic_cast 只能同于含有虚函数的类
2 dynamic_cast 会进行一个预检查,可以成功,则进行转换,不可以,返回0。
explicit :防止构造函数隐式类型转换
eg: Date a = 6;
内部其实 : Date tmp(6);
Date a(tmp) ;
最后:我们应该尽量避免使用C++的强制类型转换,强制类型转换,会自动挂起或者关闭编译器的正常的类型检查 在强制类型出错的地方不会发出警告