zoukankan      html  css  js  c++  java
  • C++学习之路: 智能指针入门

    引言: 编写智能指针的要点:
    a) 构造函数接收堆内存
    b) 析构函数释放内存
    c) 必要时要禁止值语义。
    d) 重载*与->两个操作符

     1. 简易的智能指针 。

     1 #ifndef START_PTR_H
     2 #define START_PTR_H
     3 
     4 #include <iostream>
     5 using namespace std;
     6 
     7 class Animal 
     8 {
     9     public:
    10         Animal() { cout << "Animal" << endl ;}
    11         ~Animal() { cout << "~Animal" << endl; }
    12 
    13         void run() { cout << "Animal is running....." << endl; }
    14 
    15 }; 
    16 
    17 
    18 
    19 
    20 class SmartPtr
    21 {
    22     public:
    23         SmartPtr(Animal * ptr = NULL)  //采用缺省函数
    24             :ptr_(ptr) 
    25         {
    26              
    27         }
    28 
    29         ~SmartPtr()
    30         {
    31             delete ptr_ ;
    32         }
    33 
    34         Animal &operator*()   //需要重载一个const 版本,否则const SmartPtr 无法解引用
    35         {
    36             return *ptr_ ;
    37         }
    38         const Animal &operator*() const
    39         {
    40             return *ptr_ ;
    41         }
    42 
    43         Animal *operator->()
    44         {
    45             return ptr_ ;
    46         }
    47         const Animal *operator->() const
    48         {
    49             return ptr_ ;
    50         }
    51     private:
    52         SmartPtr(const SmartPtr &other)
    53             :ptr_(other.ptr_)
    54         {
    55             
    56         }
    57         SmartPtr &operator=(const SmartPtr &other) 
    58         {
    59             if(this != &other)
    60             {
    61                 ptr_ = other.ptr_ ;
    62             }
    63             return *this ;
    64         }
    65 
    66         Animal *ptr_ ;
    67 } ;
    68 
    69 #endif  /*START_PTR_H*/

    测试代码:

     1 #include "smartPtr.h"
     2 #include <iostream>
     3 using namespace std;
     4 
     5 int main(int argc, const char *argv[])
     6 {
     7     {
     8         SmartPtr ptr(new Animal) ;      //这个智能指针的生命周期仅限于这个花括号内部, 那么它  持有的对象  的生命  也只存在于这对括号中
     9         ptr->run() ;                    //因为智能指针的构造函数中对持有对象进行定义,  在智能指针析构 函数中对持有对象进行释放, 实现自动化管理获取的资源
    10     }
    11     return 0;
    12 }
    Animal
    Animal is running.....
    ~Animal

    打印结果, 智能指针实现了内存的自动管理

    总结:

    其实

    智能指针是个类对象,但是行为表现的像一个指针。它有三种操作符

    a)      . 调用的是智能指针这个对象本身的方法。

    b)     * 调用的是  解引用出持有的对象

    c)      -> 调用的是 调用持有对象内部的成员

    2. 上例代码十分的笨拙, 对于每一类都需要都要手写一个智能指针, 重复的代码十分冗余, 所以我们采用模板技术重新改写;

    对于指向每个对象(例如string s1,s2)只产生 对应的 一类 智能指针

    先理清一个概念  CountPtr  ptr1(s1), 和 CountPtr ptr(s2) 两个智能指针 指向的类型相同,但是对象不同, 所以它们的计数也不同

    我们在 用CountPtr ptr3(ptr2),当我们构造拷贝 ptr2 给ptr3 时, ptr2和ptr3 会共享一个计数器,计数为2.

    但是ptr1的计数还是1.

    当计数为0时, 再执行析构。

      1 #ifndef COUNTER_HPP
      2 #define COUNTER_HPP
      3 
      4 template <typename T>
      5 class CounterPtr
      6 {
      7 public:
      8     typedef T value_type;
      9     typedef T *pointer;
     10     typedef const T *const_pointer;
     11     typedef T &reference;
     12     typedef const T &const_reference;
     13 
     14     explicit CounterPtr(T *p = NULL);
     15     CounterPtr(const CounterPtr<T> &other);
     16     ~CounterPtr();
     17 
     18     CounterPtr<T> &operator=(const CounterPtr<T> &other);
     19 
     20     reference operator*() const throw() 
     21     { 
     22         return *ptr_; 
     23     }
     24     
     25     pointer operator->() const throw()
     26     { 
     27         return ptr_; 
     28     }
     29 
     30     size_t count() const { return *count_; }
     31 
     32     void swap(CounterPtr<T> &other) throw()
     33     {
     34         std::swap(ptr_, other.ptr_);
     35         std::swap(count_, other.count_);
     36     }
     37 
     38     void reset() throw()
     39     {
     40         dispose();
     41     }
     42 
     43     pointer get() const throw()
     44     {
     45         return ptr_;
     46     }
     47 
     48     bool unique() const throw()
     49     {
     50         return *count_ == 1;
     51     }
     52 
     53     operator bool()
     54     {
     55         return ptr_ != NULL;
     56     }
     57 
     58 private:
     59 
     60     void dispose()
     61     {
     62         if(--*count_ == 0)
     63         {
     64             delete ptr_;
     65             delete count_;
     66         }
     67     }
     68 
     69 
     70     T *ptr_;
     71     size_t *count_; //引用计数 思考为什么采用指针 同样也不可以使用 static个对象,否则 对于一种类 我们智能指向它一个对象,例如string s1,s2 假如我们指向了 s1, 那么再指向 s2时 共用了 计数,                                                                                              出现逻辑错误
     72 };
     73 
     74 template <typename T>
     75 CounterPtr<T>::CounterPtr(T *p)
     76     :ptr_(p), count_(new size_t(1))
     77 {
     78 
     79 }
     80 
     81 template <typename T>
     82 CounterPtr<T>::CounterPtr(const CounterPtr<T> &other)
     83     :ptr_(other.ptr_), count_(other.count_)
     84 {
     85     ++*count_; //引用计数+1
     86 }
     87 
     88 template <typename T>
     89 CounterPtr<T>::~CounterPtr()
     90 {
     91     dispose();
     92 }
     93 
     94 template <typename T>
     95 CounterPtr<T> &CounterPtr<T>::operator=(const CounterPtr<T> &other)
     96 {
     97     ++*other.count_; //先对other进行+1,这样不用处理自身赋值
     98     dispose();
     99     ptr_ = other.ptr_;
    100     count_ = other.count_;
    101 
    102     return *this;
    103 }
    104 
    105 
    106 
    107 #endif //COUNTER_HPP

    以上代码 不过是例1的升级版, 用模板计数实现, 不过再此基础上又增加了一个多对象共用的 计数器, 为了防止其中一个 智能指针删除 持有对象后, 其他智能指针变成悬垂指针, 所以只有当计数器为0时我们才释放 资源(即持有资源)。

    这个模板 有一个错误使用的例子。

     1 #include "CountPtr.hpp"
     2 #include <iostream>
     3 #include <string>
     4 #include <vector>
     5 
     6 using namespace std;
     7 
     8 int main(int argc, const char *argv[])
     9 {
    10     string s1("hello");
    11     string s2("world") ;
    12     CountPtr ptr1(s1) ;  //ptr1 ->s1
    13     CountPtr ptr2(ptr1) ;  //利用ptr1 的拷贝构造函数 构造了ptr2 ,计数++
    14 
    15     //错误使用
    16     CountPtr ptr3(s1) ;  //此处我们又创建一套指针系统,因为我们是手动调用构造函数 创建新的 智能指针去指向 s1, 新的计数器 初始化计数为1;
    17                         //ptr3 是独立于 ptr1 和 ptr2 的新的一套指针, 它的存在也没有让ptr1 和ptr2得知, 所以ptr2 和 ptr1 计数也没有增加;
    18                         //当 ptr3 释放掉 s1, 那么ptr1 和ptr2 这整套指针系统全部都悬空, 导致逻辑错误 切记此例
    19 
    20     return 0;
    21 }

    上例不是一种标准 实现, C++是一种复杂的 语言, 有很多的坑, 可能单独实现某功能是对的, 但是它不能作为 一套系统的 工具, 会出现许多 难以察觉的 bug,

    为了避免这些bug, 我们追求一种标准实现

     1 #include "CountPtr.hpp"
     2 
     3 using namespace std;
     4 
     5 int main(int argc, const char *argv[])
     6 {
     7     CountPtr ptr1(new string("hello world")) ; //智能指针指向的内存都是堆上内存, 
     8                                               //我们上例的智能指针指向的 string 是在main中定义的,在栈里,是一种错误
     9     CountPtr ptr2(ptr1) ;                     //当智能指针析构时会delete string, 这是未定义行为, 是错误的, 所以智能指针必须指向处于堆内存的对象
    10 
    11     CountPtr ptr3(make_ptr(string)) ;
    12     return 0;
    13 }

    上述 6,9,12 都是标准实现, 除此之外, 不要自己写出奇葩的实现。

    3. 了解了智能指针的实现以后, 我们看下强大的C++11标准给我们提供了哪些智能指针,及接口的使用和注意事项

    a) scoped_ptr

    的实现并不复杂, 和我们例2的的实现原理相同, 不过加了很多的异常处理和接口保护, 它是一款真正的工业级代码, 并不是我们写出来的玩具

      有兴趣可以去boost库一探究竟

     1  1 #include <iostream>
     2  2 #include <boost/scoped_ptr.hpp>
     3  3 using namespace std;
     4  4 using namespace boost;
     5  5 
     6  6 
     7  7 class Test
     8  8 {
     9  9 public:
    10 10     Test() { cout << "Test" << endl;}
    11 11     ~Test() { cout << "~Test" << endl;}
    12 12 };
    13 13 
    14 14 int main(int argc, char const *argv[])
    15 15 {
    16 16     scoped_ptr<Test> ptr(new Test);
    17 17     return 0;
    18 18 }

    结果打印:

    Test
    ~Test

    scoped_ptr是一种最简单的智能指针, 它不可复制也不可赋值。

    b)Unique_ptr
    下面介绍一下scoped_ptr的加强版, C++11标准中提供了一种更加强大的智能指针,它和scoped_ptr的区别在于 添加了 右值引用 和(move)语义;
    它也是不可复制和不可赋值类。 但是拥有移动复制, 和移动赋值的能力
    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    
    class Test
    {
    public:
        Test() { cout << "Test" << endl;}
        ~Test() { cout << "~Test" << endl;}
    };
    
    int main(int argc, const char *argv[])
    {
        unique_ptr<Test> ptr(new Test);
    
        //unique_ptr<Test> ptr2(ptr); //没有拷贝构造
        //unique_ptr<Test> ptr2;
        //ptr2 = ptr;
    
        unique_ptr<Test> ptr2(std::move(ptr));
        unique_ptr<Test> ptr3;
        ptr3 = std::move(ptr2);
        return 0;
    }

    同样打印正确, 这里不在演示。

    编译时记得 加-std=c++0x 

    下面验证一下确实Unique 拥有移动赋值的能力, 看下它的机制。

     1 #include <iostream>
     2 #include <memory>
     3 #include <vector>
     4 using namespace std;
     5 
     6 class Test
     7 {
     8 public:
     9     Test() { cout << "Test" << endl;}
    10     ~Test() { cout << "~Test" << endl;}
    11 
    12     Test(Test &&t) { cout << "move" << endl; }
    13     Test &operator=(Test &&t)
    14     {
    15 
    16     }
    17 
    18 private:
    19     Test(const Test &);
    20     void operator=(const Test &);
    21 };
    22 
    23 int main(int argc, char const *argv[])
    24 {
    25     vector<Test> coll;
    26     coll.push_back(Test());
    27     return 0;
    28 }


    结果打印:
    Test
    move
    ~Test
    ~Test
    
    

    coll.push_back(Test()), 这一行调用构造函数构造了一个temp零时变量,然后用右值移动构造函数 把 temp变量的值 移动给 coll中的元素。 然后零时变量失效,无所谓,temp也只能在26行存活。

    这样便节省了 复制temp的开销。

    如果是旧标准的话:

    temp 变量的构造以及 复制temp 将会打印出 两次Test ;但是实验证明我们只构造了一次Test即达到了目的,节约了一次开销。

    但是temp变量的 析构却在所难免。

    c) unique_ptr正是拥有右值传递的能力,即使它不可复制和不可赋值 我们还可以用vector容器来装很多unique_ptr对象
     1 #include <iostream>
     2 #include <memory>
     3 #include <vector>
     4 using namespace std;
     5 
     6 class Test
     7 {
     8 public:
     9     Test() { cout << "Test" << endl;}
    10     ~Test() { cout << "~Test" << endl;}
    11 
    12 private:
    13     Test(const Test &);
    14     void operator=(const Test &);
    15 };
    16 
    17 int main(int argc, char const *argv[])
    18 {
    19     vector<unique_ptr<Test> > coll;
    20     coll.push_back(unique_ptr<Test>(new Test));
    21     return 0;
    22 }
    
    

    打印结果:正确

    1 Test
    2 ~Test

    我们可以看到, Test对象并没有析构两次, 在以前的久标准中,情况如下

    在20行我们创建了一个unique_ptr的temp变量, 它指向一个在堆上的Test的对象, 这时 coll复制了 unique_ptr, 然后temp销毁,Test 被复制版的unique_ptr(存在于coll中)重新指向,所以没有析构

    在新标准中添加了move 右值移动赋值以后:

    unique_ptr构建temp变量以后, 直接被move到 college中, 省去了复制和析构temp的开销。



  • 相关阅读:
    webpack简介与使用
    webpack使用小记
    H5常用技巧
    mac 终端 常用命令
    vue.js学习资料
    git clean(转载)
    HTML5 移动端的上下左右滑动问题
    HTML5+CSS3 loading 效果收集--转载
    使用Chrome DevTools的Timeline分析页面性能
    phantomjs 是什么?----主要是mac下面
  • 原文地址:https://www.cnblogs.com/DLzhang/p/4014811.html
Copyright © 2011-2022 走看看