范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

C手把手带你实现一个智能指针

  为什么需要智能指针?
  这个问题承接上一篇文章《C++ 堆,栈,RAII》,在RAII的指引下,我们需要实现一个类来管理资源,将资源和类对象的生命周期进行绑定,这样我们就可以不用手动释放资源了。
  那为什么把类实现成  like pointer  ?
  上篇文章中说道,因为C++存在对象切片,而使用指针就避免了这个问题。
  我们现在来实现一个智能指针:
  首先,我们应该满足资源管理的需求: 构造函数获得资源,析构函数释放资源。 class Type { };  class smart_ptr { public:     smart_ptr(Type* ptr = NULL) : m_ptr(ptr) {}     ~smart_ptr() {         delete m_ptr;     } private:     Type* m_ptr; };  smart_ptr(new Type);
  好了,我们这样就实现了一个可以管理 Type 类指针的  smart_ptr  。
  显然,我们这个代码不够通用,只能管理 Type这个类型,我们把 smart_ptr 改成模板,把类型传进去,这样就通用了。 template class smart_ptr { public:     smart_ptr(T* ptr = NULL) : m_ptr(ptr) {}     ~smart_ptr() {         delete m_ptr;     } private:     T* m_ptr; };
  我们只需要把 smart_ptr(new Type);   改成  smart_ptr(new Type);  ,就对Type进行了管理。
  这样我们就实现了资源管理的功能,下面我们还需要实现 like pointer  功能。
  指针有什么操作? T& operator*() const; T* operator->() const; operator bool() const;
  我们需要实现指针的解引用, ->   运算,  bool  运算,  bool  运算这里涉及到一个隐式转换的问题,有机会再说。 注意这里函数括号后面加上 const  ,表示当前成员函数不改变对象成员,这是个好习惯,不清楚的话可以看下我之前的文章。 template class smart_ptr { public:     smart_ptr(T* ptr = NULL) : m_ptr(ptr) {}     ~smart_ptr() {         delete m_ptr;     }     T& operator*() const { return *m_ptr; }     T* operator->() const { return m_ptr; }     operator bool() const { return m_ptr; } private:     T* m_ptr; }; // 测试 class Type { public:     int a = 1; };  smart_ptr sptr(new Type); cout << sptr->a << endl;        // 1 cout << (*sptr).a << endl;      // 1 if (sptr) {                     // true     cout << "true" << endl; }
  这样,我们就初步实现了指针的这几个行为。
  一般类内包含一个指针,这种设计叫委托,类的功能基本上都是由类内这个指针来完成的,可以理解为委托它实现的。
  类内包含指针的话,就少不了三个函数,拷贝构造,拷贝赋值,析构函数。
  有指针存在,就涉及到浅拷贝还是深拷贝的问题,如果不写拷贝构造,用到了拷贝函数时,编译器会提供一个默认的拷贝构造,而这个默认的拷贝构造做的事情是把你的成员变量逐个拷贝下来。
  显然,类内的指针就被copy了一份,新的对象中就有一个新拷贝的指针了,然而这个指针和被拷贝的对象里的指针一样,都是指向原指针的地址。 如果原对象指针所指的空间被释放了,这个拷贝对象指针再去操作这块空间,结果可想而知。
  所以,有指针的话则需要我们手动去定义一个拷贝构造,在拷贝构造内申请空间,把原指针的内存空间copy过来,新对象里的指针指向这块空间。
  拷贝赋值的操作是这样的:  b = a;   把 a的值赋值给b,首先我得把b内指针占用的空间给释放掉,再把a内指针指向的空间拷贝一份,再将b内指针指过去,这样就完成了拷贝赋值,这里有个问题值得注意的是,如果 b 和 a指向的是同一片区域,相当于把自己赋值给自己,根据上面的操作,先把内存给释放了,在下一步拷贝时则会引发异常,所以,我们在释放空间之前,得先判断一下咱是不是自己啊!
  析构函数就很好理解了,有指针指向堆空间,最后肯定要在析构函数内释放内存啦。
  所以,类内包含指针的话这三个函数缺一不可,其他的函数则根据需要进行补充。
  现在回归我们的话题,智能指针需要实现拷贝构造,拷贝赋值吗?
  最简单的方式就是禁用拷贝构造,拷贝赋值。 如果我们不写拷贝构造,拷贝赋值,编译器就会提供一个默认的浅拷贝,我们一用,就会两次析构同一块内存引发异常。 就像下面这样: class Type { public:     int a = 1; };  template class smart_ptr { public:     smart_ptr(T* ptr = NULL) : m_ptr(ptr) {}     ~smart_ptr() {         delete m_ptr;     }     T& operator*() const { return *m_ptr; }     T* operator->() const { return m_ptr; }     operator bool() const { return m_ptr; } private:     T* m_ptr; };  int main() {     smart_ptr sptr(new Type);     smart_ptr sptr2(sptr);     return 0; }
  所以,我们需要显式把这两个函数给删掉,C++11 提供的  =delete   就可以用上了 class Type { public:     int a = 1; };  template class smart_ptr { public:     smart_ptr(T* ptr = NULL) : m_ptr(ptr) {}     ~smart_ptr() {         delete m_ptr;     }     // 加上下面这两行     smart_ptr(const smart_ptr&) = delete;     smart_ptr& operator=(const smart_ptr) = delete;      T& operator*() const { return *m_ptr; }     T* operator->() const { return m_ptr; }     operator bool() const { return m_ptr; } private:     T* m_ptr; };  int main() {     smart_ptr sptr(new Type);     smart_ptr sptr2(sptr);  // 这行就直接编译不过了     return 0; }
  这样就杜绝了这两个函数被调用。
  第二种方案,我们需要去实现拷贝构造和拷贝赋值,问题是要怎么实现这两个函数,我们要不要在智能指针拷贝时把对象拷贝一份?
  不行,通常大家都不会这么用,因为使用智能指针的目的就是要减少对象的拷贝,所以我们要实现拷贝就有两种方式了。 在拷贝里面实现搬移的操作,把原指针的对象赋值给新指针,原指针就不能再用了,对应 std::unique_ptr  原指针和新指针都指向那一个对象,在智能指针里添加一个引用计数,引用计数为0后删除该对象,对应 std::shared_ptr
  下面我们实现一个搬移的操作。 #include  using namespace std;  class Type { public:     int a = 1; };  template class smart_ptr { public:     smart_ptr(T* ptr = NULL) : m_ptr(ptr) {}     ~smart_ptr() {         delete m_ptr;     }     T& operator*() const { return *m_ptr; }     T* operator->() const { return m_ptr; }     operator bool() const { return m_ptr; }      // 搬移构造     smart_ptr(smart_ptr&& rhs) noexcept {         m_ptr = rhs.m_ptr;         rhs.m_ptr = NULL;     }     // 搬移赋值     smart_ptr& operator=(smart_ptr&& rhs) noexcept {         m_ptr = rhs.m_ptr;         rhs.m_ptr = NULL;         return *this;     } private:     T* m_ptr; };  int main() {     smart_ptr sptr(new Type);     smart_ptr sptr2(std::move(sptr));  // 调用搬移构造     smart_ptr sptr3;     sptr3 = std::move(sptr2);  // 调用搬移赋值     return 0; }
  根据C++的规则,我们提供了搬移构造而没有提供拷贝构造,那拷贝构造就自动被禁用。
  搬移构造需要的参数是一个右值,我们借用标准库的  std::move()   函数让它帮我们把  sptr   转型为一个右值,我们在构造函数内把 rhs的指针给拿过来,再把rhs指向空,这时rhs以后再也不能被用了,因为它的资源已经被我偷过来了。 类似的,搬移赋值也是一样的道理。
  如果你发现我在 move function   后加了  noexcept   关键字,这对于智能指针能被正确使用是十分必要的,我会在以后的文章中说明。
  这样我们就基本实现了一个  unique_ptr   的功能。
  unique_ptr   算是一种较为安全的智能指针了。 但是,一个对象只能被单个  unique_ptr   所 拥有 ,这显然不能满足所有使用场合的需求。 一种常见的情况是,多个智能指针同时拥有一个对象;当它们全部都失效时,这个对象也同时会被删除。这也就是  shared_ptr   了。 unique_ptr   和  shared_ptr    的主要 区别如下图所示:
  多个不同的  shared_ptr   不仅可以共享一个对象,在共享同一对象时也需要同时共享同一个 计数 。 当最后一个指向对象(和共享计数)的  shared_ptr   析构时,它需要删除对象和共享计数。 我们下面就来实现一下。
  我们需要实现一个 share_count   类,用来处理引用计数增加减少的操作,这里没有考虑计数器的线程安全性。 class share_count { public:     share_count() : _count(1) {}     void add_count() {         ++_count;     }     long reduce_count() {         return --_count;     }     long get_count() const {         return _count;     } private:     long _count; };
  下面我们实现一下构造函数,拷贝构造,拷贝赋值,析构函数,具体实现如下。 class Type { public:     int a = 1; };  class share_count { public:     share_count() : _count(1) {}     void add_count() {         ++_count;     }     long reduce_count() {         return --_count;     }     long get_count() const {         return _count;     } private:     long _count; };  template class smart_ptr { public:     smart_ptr(T* ptr = NULL) : m_ptr(ptr) {         if (ptr) {             m_share_count = new share_count;         }     }     ~smart_ptr() {         if (m_ptr && !m_share_count->reduce_count()) {             delete m_ptr;             delete m_share_count;             cout << "~smart_ptr" << endl;         }     }     T& operator*() const { return *m_ptr; }     T* operator->() const { return m_ptr; }     operator bool() const { return m_ptr; }      smart_ptr(const smart_ptr& rhs) noexcept {         m_ptr = rhs.m_ptr;         m_share_count = rhs.m_share_count;         m_share_count->add_count();     }     smart_ptr& operator=(const smart_ptr& rhs) noexcept {         m_ptr = rhs.m_ptr;         m_share_count = rhs.m_share_count;         m_share_count->add_count();         return *this;     }     long use_count() const {         if (m_ptr) {             return m_share_count->get_count();         }         return 0;     } private:     T* m_ptr;     share_count* m_share_count; };  int main() {     smart_ptr sptr(new Type);     cout << "sptr"s share_count:" << sptr.use_count() << endl;      smart_ptr sptr2(sptr);     cout << "sptr"s share_count:" << sptr2.use_count() << endl;      smart_ptr sptr3;     sptr3 = sptr2;     cout << "sptr"s share_count:" << sptr3.use_count() << endl;     return 0; } sptr"s share_count: 1 sptr"s share_count: 2 sptr"s share_count: 3 ~smart_ptr
  我们看到最后被正确的执行析构了,这样我们就基本实现了一个  shared_ptr   的功能了。
  智能指针浅显的写法就模拟到这里了,可能在具体的场景下还缺少一些功能,根据需求我们可以再增加嘛!
  好了,智能指针先说到这里了,如果文章有错误的地方还请给我指出来,大家一起进步嘛。
  如果觉得对你有帮助的话请@程序员杨小哥 点个赞,谢谢!

iPhoneSE3在国内市场遇冷销量只有6000多台中关村在线消息3月9日凌晨两点,苹果召开了新品发布会,发布了消费者期待已久的iPhoneSE3,但是国内的售价太高了,比上一代还贵了200元,配上年8年前的复古造型,在国内遇冷是很iOS15。4续航崩了,24小时耗电80,120Hz是罪魁祸首?iOS虽然苹果iOS在玩家当中的口碑最高,但实际上每一次新版本升级都像是一次梭哈,没有BUG自然是皆大欢喜,出了BUG就得满脸黑线。最近苹果刚推送了iOS15。4更新包,不少玩家都千元手机选购指南,华为小米一加oppo你会选哪个经过一个寒假的时间,学生党陆续准备开学,手里的压岁钱准备好怎么花了吗?需要换机的学生党就会有一些纠结,要买什么样的手机才能满足自己的日常需求又能够长时间的使用,下面这篇文章据盘点一买手机不懂配置怎么办这三款推荐给你两款骁龙888一款天玑8100印象中,骁龙888处理器也刚发布不久,最新的天玑处理器也已经发布了,现在手机的更新换代是越来越快了,你刚买的手机没过多久可能就已经过时了,但是过时不代表性能过时,现在的手机时代是一仅售3299元,12GB256GB前置6000万,柔性屏旗舰继续维持低价罗永浩或将重返科技圈的消息再度引发了热议,靠直播成功还清债务的罗永浩,无论后续进入哪个行业大概率都能做出一番成绩,其实让罗永浩背负巨额债务的锤子科技手机之前也曾风光无限,无奈小公司iOS15。4续航翻车,不敢升级?看完9款iPhone耗电实测再做决定iOS15。4作为苹果今年最大的一次系统更新,引起了不少果粉的关注,很多人第一时间升级了iOS15。4,希望可以早一点享受到新系统带来的出色体验,但是仍有很多果粉处于观望状态,觉得iPhone13的新颜色真的好绿在3月份苹果突然说iPhone13居然有了大家都爱的颜色,相信绿色的出现可以为这个时间多一点爱。前几年,iPhone12和iPhone12mini推出了新的紫色。今年,苹果推出了两2022款iPhoneSE是小屏之王,先打赢这款手机再说最近,苹果终于发布了新款iPhoneSE,很多人称这款手机为小屏之王,对于不是很懂手机的朋友,可能就会信了这个说法,从而去购买这款手机,如果真的这样那就亏大了,因为同样在3000多iPhone14用老A15芯片,高配才用A16?库克是怎么想的?众所周知,iPhone中,最有优势的硬件,其实是那颗A系列芯片。因为与安卓芯片相比,不说领先2代,领先1代是没有问题的,举例说明一下,苹果上一代的A14就可以打平这一代的高通骁龙8150W闪充天玑8100处理器!realmeGTNeo3今天发在今天下午2点,realme官方将召开新品发布会,而主角就是GTNeo3,目前关于这款新机的核心配置,官方几乎已经全部曝光了,只剩下售价这个最大悬念。这款新机将对标红米K50,下面受原材料价格上涨影响,宁德时代动态调整部分电池价格新京报贝壳财经讯(记者王琳琳)3月21日,有消息称,一家新能源车企相关负责人在接受媒体采访时表示,自去年下半年至今宁德时代动力电池涨价两次。宁德时代相关负责人对新京报贝壳财经记者表
惊喜还是惊吓?全球首批活体机器人能生崽了不少科幻片中都出现过机器人繁衍生息的剧情,而如今这样脑洞大开的情景似乎已成为了现实。近日,美国科学家借助超级计算机,用青蛙细胞打造出的活体机器人,首次完成了自我繁殖,还能四世同堂。任正非兑现承诺,华为海思开始招聘中科院出身的联想,近日的处境不太好,自从科创板上市一日游后,便成为了网友关注的焦点,尤其是司马南不断揭露联想真面目后,彻底引爆了舆论界,时事评论家胡锡进也于近日改变自己的立场,明确腾讯成功注册狗头表情包商标,柴犬本尊刚过16岁生日,曾经被主人弃养从去年开始,微信QQ微博等各大平台相继推出狗头表情包,成为笑哭表情包的强力竞争对手。企查查App显示,近期,腾讯申请的狗头商标正式完成注册,商标国际分类涉身器材家具等。早在今年2月怎么联系微信客服电话微信终于有人工客服了,打这个号码!平时大家用微信可能会遇到各种各样的问题,比如账号被禁转错账等等问题,这时候很多人都想第一时间找到微信的人工客服。网上随便一搜,就有很多人找不过在微联想比华为早成立3年有中科院背景,为何现在被华为甩得那么远?很简单华为老总任正非是真正爱国的企业家,目光长远联想教父柳传志是资本家,只看眼前利益!任正非想的是为国为民,柳传志想的是自己及少数关系人!这个很简单,一个为自己的钱包,一个是为大家有没有买过二手单反相机的说下体会?我从来没有买过,因为我有一定程度的洁癖,不喜欢别人用过的东西。不过,我们摄影圈子里,还是有不少人买二手摄影器材,包括单反。有个人就是这样,买了别人升级淘汰下来的尼康d810,以及尼会不会有人利用京东七天无理由退货无限用新手机的?这样的恶意退货行为是存在的。依稀记得,有个帖子,曾有网友晒出自己在某电商平台的退货记录,从价值几百元到数万元的电子产品。该网友利用电商平台的免费退货规则,免费体验了各式各样的数码产聚焦精准医疗建设智慧品牌来源人民网人民日报推进品牌建设,是一项打好基础利在长远的战略性工程,也是高质量发展的重要抓手。为解决医疗研发和治疗领域的效率问题,2014年医渡科技开启创业征程,始终坚守安全价值普想说爱小米手机不容易我不是华为粉也不是米粉,只是作为一个普通的消费者,首先爱国是必须的!米国之前没制裁华为的时候,以前还一直用苹果,从苹果4到苹果6S都用,一直都是新机就买,算是半个果粉。制裁之后就用爆料!iPhone13出现红绿双色屏Bug小米12全球首发骁龙8Gen1抢首发!小米12全球首发骁龙8Gen1小米CEO雷军于12月2日再次强调小米12骁龙8代全球首发,并发表长文强调首发是实力,也是承诺。近日,高通如期举行骁龙技术峰会,会上正式发布了基站机房里拔掉一根光纤会有什么后果?那就看运气了,如果你正好拔掉一根是PTN传输光纤很有可能本站传输所下挂基站全部断站,如果是环路的话不会断站,但后台告警马上就出来,没有自动恢复就会有工单发到铁塔代维,代维会快速去现