28.智能指针

所谓智能指针,就是看起来用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; }
}
  • 但是建议:不要提供转原指针的隐式转换操作符,除非不得已,因为回避了指针智能设计的目的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。