所谓智能指针,就是看起来用i起来感觉起来都像内建指针,但提供更多的机能的一种对象
当使用智能指针取代内建指针
将获得以下指针行为的控制权:
- 构造和析构。
可以决定产生或销毁时的动作,例如: 给指针默认值0避免未初始化,或提前删除指针所指对象避免泄露。 - 复制和赋值
可以控制智能指针被复制时,自动将其所指对象进行复制,即深拷贝,或者禁止复制赋值操作。 - 解引
解引(取用)所指对象时使用lazy策略
智能指针对象模型
- 如果想禁止智能指针赋值或复制,可以将他们声明为private
- 两个解引用都应该声明为const函数(相当于只读模式),因为解引指针所指对象并不会改变成员变量指针本身。
template<class T>
class SmartPtr {
public:
SmartPtr(T * realPtr = 0); // 默认为0
SmartPtr(const SmartPtr & rhs); // 复制一个智能指针
~SmartPtr(); // 销毁一个智能指针
SmartPtr & operator=(const SmartPtr & rhs); // 赋值一个智能指针
T * operator->() const; // 解引
T & operator*() const; // 解引
private:
T * point;
};
智能指针的构造 赋值 析构
析构
- 当智能指针拥有它所指的对象,它有责任在本身即将被析构之前删除该对象
- STL库中提供了
auto_ptr
智能指针,用来指向一个诞生于heap内的对象,直到auto_ptr被销毁时,析构函数会删除其所指物
auto_ptr::~auto_ptr() { delete pointee; }
复制或赋值
- 如果我们只是复制其内的指针(浅拷贝), 会导致2个auto_ptr指向同一对象,当两个auto_ptr析构时,这个对象可能会被删除两次
- 另一种做法是以new操作符为所指对象产生一个新的副本(深拷贝),但缺点是使新对象的诞生形成
性能冲击
-
auto_ptr
采用了更富有弹性的解法,当auto_ptr被复制或者被赋值时,转移对象拥有权
template<class T>
class auto_ptr {
public:
auto_ptr(auto_ptr<T>& rhs);
auto_ptr<T>& operator=(auto_ptr<T>& rhs);
T* pointee;
};
template<class T>
auto_ptr<T>::auto_ptrt(auto_ptr<T>& rhs) // copy constructor
{
pointee = rhs.pointee;
rhs.pointee = 0;
}
template<class T>
auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs) // assignment operator
{
if (this == &rhs) return *this; // 如果自己赋值给自己
delete this->pointee; // 必须先删除目前拥有物
this->pointeee = rhs.pointee; // 转移所有权
rhs.pointee = 0;
return *this;
}
- 注意:赋值时,在掌握一个新对象拥有权前必须先删除它所拥有的对象,否则该对象在内存中永远不会被删除
- 注意:由于这里的拷贝构造函数与赋值函数是
非传统式声明
,会修改复制的来源端内容,被调用时会转移对象的拥有权,所以以值传递
的方式传递时,会出现异常,因此auto_ptr对象做参数时,const + & 才是最合适的传递方式
。
拷贝构造函数与赋值函数的传统式声明:通常形参中的rhs参数属于const属性,在复制或赋值时不会改变参数内容
值传递的方式传递auto_ptr对象时:实参以值传递形式传给形参时,调用了拷贝构造函数,将对象所以权转给了形参,同时实参不在指向任何对象,而形参处于局部变量,函数结构时调用析构函数又将传过来的对象删除了,如果之后再想使用实参会导致未定义行为。
智能指针的解引操作
-
operator*
和operator->
函数,注意他们返回值的差异 -
operator*
返回的是引用形式
,因为pointee不一定非得指向类型为T的对象不可,它也可以指向一个T的派生类型的对象,如果返回值是对象形式,例如:指针指向的是子类,解引后返回的是父类类型,就会涉及类型切割问题
,造成之后再用该对象调用虚函数失败,无法支持虚函数
。
template<class T>
T& SmartPtr<T>::operator*() const
{
return *pointee;
}
-
operator->
大部分时候,只需要返回一个普通的指针
即可,因为返回的是指针,所以智能指针很方便的通过operator->
调用虚函数。
template<class T>
T* SmartPtr<T>::operator->() const
{
return pointee;
}
智能指针判断是否为NULL操作
- 为了实现以下功能, 我们还需要为智能指针提供一个
隐式类型转换操作符
和operator!
操作
SmartPtr<TreeNode> ptn;
if (ptn == 0)
if (!ptn)
if (ptn)
if (!ptn == !ptn)
-
隐式类型转换操作符operator void*
在不能直接转换为bool类型的情况下,会试图进行隐式转换为(void*)
指针的操作
template<class T>
int SmartPtr<T>::operator void*()
{
if (this->pointee == NULL) return 0;
return 1;
}
- 在C++STL中,隐式转换为
void*
已被隐式类型转换操作符operator bool
取代,在不能直接转换为bool类型的情况下,会试图隐式转换为bool - 同时还增加了
bool operator!()
进行互补完善
智能指针转换为非智能指针
- 当使用了智能指针,但一些老旧函数并非使用智能指针时,就需要提供一条智能指针转换原指针的方法,例如:
Smart_ptr<ListNode> pt;
func(pt); // 失败, 因为没有提供 Smart_ptr<ListNode> 转 ListNode* 的方法
- 要使上式通过,需要加上一个隐式类型转换操作符
template<class T>
class Smart_ptr {
public:
operator T*() { return pointee; }
}
- 但是建议:
不要提供转原指针的隐式转换操作符,除非不得已
,因为回避了指针智能设计的目的