首先回顾点知识点(以下是我自己的想法,如有错误,请在评论区多多指正):

1.如果一个内置指针被别的指针接管资源,那么它必须置于安全析构状态(nullptr或者0都可以)

2.如果一个容器被接管资源,那么容器要执行clear(),清理自己的元素(c++primer5th就是这么做的)

3.如果是一个内置类型或者string类型,被接管了什么也不用管。

4.如果析构的时候需要利用或者改变内置指针的值,此时一定要衡量好,是否这个内置指针可能在代码中被析构过,如果有那么必须检测此时这个指针是否为空(前面析构完的都置为nullptr)

5.移动构造函数是接管资源,如果是类似指针指针的类,此时引用计数不变。

6.移动赋值运算符也一样,只是接管资源,应用计数部分的内存也接管即可,不用增加也不用减少。

7.释放资源或者是改变资源的值得时候,一定要判断:这个资源的值是否可能被释放.如果出错,是及难找到错误的,例子就是hasptr类指针版本的网上的随书源代码。里面析构函数定义就这样的错误,本贴正是解决这个错误的,这个错误困扰我一天,我最后还在淘宝请教一位大神,才解决(当然,也有我自己点功劳,哈哈)。

8.拷贝赋值运算符或者移动赋值运算符如果既有释放,也有赋值,一定要组织好释放和赋值的顺序,既要解决自赋值问题,有要解决正确释放问题(比如hasptr类指针版本)。

9.赋值的时候,在编译器查找函数参数匹配时候,一定会调用构造函数,一般是左值拷贝,右值移动。例如
  i = 1234;  //1234是右值,调用移动构造函数拷贝

  j = i;        //i是左值,调用拷贝构造函数拷贝

都是赋值,可能调用不同的函数(除非拷贝赋值运算符和移动赋值运算符是同一个或者没有定义移动构造函数)

10.只能移动不能拷贝的,比如iostream对象,unique_ptr对象,可以作为函数返回值,但是必须以引用形式返回,或者以拷贝的形式且返回临时对象(返回临时对象就可以调用移动赋值运算符)

hasptr类指针版本

#include <string>
#include <cstddef>
#include "hasptr.h"
using namespace std;HasPtr::HasPtr(const std::string &s): ps(new std::string(s)), i(0), use(new std::size_t(1)) {}// copy constructor copies all three data members // and increments the counterHasPtr::HasPtr(const HasPtr &p): ps(p.ps), i(p.i), use(p.use) { ++*use; }HasPtr::HasPtr(HasPtr &&p): ps(p.ps), i(p.i), use(p.use) { p.ps = 0; p.use = 0; }HasPtr::~HasPtr()
{if (--*use == 0) {   // if the reference count goes to 0delete ps;       // delete the stringdelete use;      // and the counter}
}HasPtr &
HasPtr::operator=(HasPtr &&rhs)
{if (this != &rhs) {if (--*use == 0) {   // do the work of the destructordelete ps;delete use;}ps = rhs.ps;         // do the work of the move constructori = rhs.i;use = rhs.use;ps = 0; use = 0;}return *this;
}HasPtr& HasPtr::operator=(const HasPtr &rhs)
{++*rhs.use;  // increment the use count of the right-hand operandif (--*use == 0) {   // then decrement this object's counterdelete ps;       // if no other users delete use;      // free this object's allocated members}ps = rhs.ps;         // copy data from rhs into this objecti = rhs.i;use = rhs.use; return *this;        // return this object
}HasPtr f(HasPtr hp) // HasPtr passed by value, so it is copied
{HasPtr ret;ret = hp;        // assignment copies the given HasPtr// proces retreturn ret;      // ret and hp are destroyed
}
//hasptr.h
#include <string>
#include <iostream>
#include <new>
using namespace std;
class HasPtr{public:HasPtr(const string &s  = string());HasPtr(const HasPtr &);HasPtr& operator=(const HasPtr &);HasPtr(HasPtr &&);HasPtr& operator=(HasPtr &&);~HasPtr();size_t use_count()const{cout <<"use_count:" << *use << endl;return *use;}private:size_t    * use;string * ps;int i;
};
#include <string>
#include <new>
#include <iostream>
#include "hasptr.h"
#include <vector>
#include <algorithm>
using namespace std;
int main()
{HasPtr h1("hasptr1");
HasPtr h2;
h2 = (h1);return 0;
}

编译没问题,运行后出现

Segment fault(cole dumped)

问题已经找到了,是因为变量多次释放造成的。两个赋值运算符指针释放后都佳nullptr,然后

析构函数里面首先判断下 !use是否有值,这样就安全了。

(网上源代码)最大的问题是代码运行过程中某些HasPtr对象(ps 和 use对象)可能被拷贝赋值运算符或者移动赋值运算符释放,如果一个对象运行过程中引用计数为1,那么会被这两个成员函数(被拷贝赋值运算符或者移动赋值运算符)释放,这些对下你给如果程序运行完毕后,执行析构函数时候,第一句执行--*use;就会执行错误(本身use指向内存已经被释放了),从而产生错误Segment fault,core dumped。

解决办法是:

每次释放后都use = nullptr,ps = nullptr,并且析构函数内部首先执行

if (!use){return ;}

这样就不会出现运行时错误了。

改正后如下:

hasptr类指针版本

文件hasptr.h

#ifndef HASPTR_H
#define HASPTR_H#include <string>
#include <iostream>
#include <new>
using namespace std;
class HasPtr{public:HasPtr(const string &s  = string());HasPtr(const HasPtr &);HasPtr& operator=(const HasPtr &);HasPtr(HasPtr &&)noexcept;HasPtr& operator=(HasPtr &&)noexcept;~HasPtr();size_t use_count()const{cout <<"use_count:" << *use << endl;return *use;}private:size_t * use;string * ps;int i;
};inline
HasPtr::HasPtr(const std::string &s): ps(new std::string(s)), i(0), use(new std::size_t(1)) {}// copy constructor copies all three data members
// and increments the counter
inline
HasPtr::HasPtr(const HasPtr &p): ps(p.ps), i(p.i), use(p.use) { ++*use; }inline
HasPtr::HasPtr(HasPtr &&p)noexcept: ps(p.ps), i(p.i), use(p.use) { p.ps = 0; p.use = 0; }inline
HasPtr::~HasPtr()
{
//you must charge whether the object is released before
//f.g.it can operate delete use and delete ps,so it is released before,it can't released again at all
if(!use)   //important{return ;}if (--*use == 0) {   // if the reference count goes to 0delete ps;       delete use;      use = nullptr;  //it'll be a minute can determine that whether it is released ps = nullptr;  }
}inline
HasPtr &
HasPtr::operator=(HasPtr &&rhs)noexcept
{//这里先判断是不是自赋值情况,然后才能做赋值运算,如果先做赋值运算,那么释放的对象就不对了if (this != &rhs) {if (--*use == 0) {   // do the work of the destructordelete ps;ps = nullptr;delete use;use = nullptr;//无论什么时候,内置指针释放后,置为nullptr是个不错的主意}ps = rhs.ps;         // do the work of the move constructori = rhs.i;use = rhs.use;    }return *this;
}
inline
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{++*rhs.use;  // increment the use count of the right-hand operandif (--*use == 0) {   // then decrement this object's counterdelete ps;ps = nullptr; // if no other users delete use; use = nullptr;    // free this object's allocated members}ps = rhs.ps;         // copy data from rhs into this objecti = rhs.i;use = rhs.use; return *this;        // return this object
}#endif

0和nullptr是一回事。作用一样。

hasptr类值版本:

#ifndef HASPTR_H
#define HASPTR_H#include <string>
#include <iostream>
#include <new>
using namespace std;
/**  注意:当你释放了某个对象的空间后,余下的部分可以交给编译器去做。但是不能让编译器再使用*  这个对象的被释放的值,否则会引发未定义的行为。比如:Segment fault,core dumped.*  比如: 执行h1 = h2; 之后,h1的资源就被拷贝赋值运算符释放掉了,此时h1(占用内存空间的值)不能再使用  *  了,最后由编译器释放就可以了.*/
class HasPtr{public:HasPtr(const string &s  = string());HasPtr(const HasPtr &);HasPtr& operator=(const HasPtr &);HasPtr(HasPtr &&)noexcept;HasPtr& operator=(HasPtr &&)noexcept;~HasPtr();private:string * ps;int i;
};
inline
HasPtr::HasPtr(const std::string &s): ps(new std::string(s)), i(0) {}// copy constructor copies all three data members
// and increments the counter
inline
HasPtr::HasPtr(const HasPtr &p): ps(new string(*p.ps)), i(p.i) {}inline
HasPtr::HasPtr(HasPtr &&p)noexcept: ps(p.ps), i(p.i){ p.ps = 0;}
inline
HasPtr::~HasPtr()
{
//you must charge whether the object is released before
//f.g.it can operate delete use and delete ps,so it is released before,it can't released again at allif(!ps)delete ps;
}inline
HasPtr &
HasPtr::operator=(HasPtr &&rhs)noexcept
{//这里先判断是不是自赋值情况,然后才能做赋值运算,如果先做赋值运算,那么释放的对象就不对了if(this != &rhs){delete ps;ps = rhs.ps;i = rhs.i;rhs.ps = nullptr;     }   return *this;
}
inline
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{string *new_ps = new string(*rhs.ps);delete ps;ps = nullptr;   //这个很重要,提现代码的严谨性,一会析构函数需要检测,检测到nullptr,不用再释放了,否则引发未定义行为ps = new_ps;i = rhs.i;return *this;        // return this object
}#endif

(学习c++primer5th的重要)c++ primer5th类指针版本hasptr (网上源代码错误) 定义错误相关推荐

  1. qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7851 ...

  2. C++学习笔记:类的成员函数的声明与定义

    今天学习一下类的成员函数,首先讲一下常规的类外的函数 写在类的外部的函数叫做全局函数,不属于任何的类. 如果写在类的里面就叫做类的成员函数 这里注意的是,类的成员函数如果加了const,就表明该函数不 ...

  3. 列表怎么有限的初始化为零_《零基础学习Android开发》第五课 类与面向对象编程1-1...

    视频:<零基础学习Android开发>第五课 类与面向对象编程1-1 类的定义.成员变量.构造方法.成员方法 一.从数据与逻辑相互关系审视代码 通过前面的课程,我们不断接触Java语言的知 ...

  4. QT学习笔记(十一):QString类

    QT学习笔记(十一):QString类 1.概述 2.编辑操作 3.查询操作 3.转换操作 1.概述 1.1 QString 类是 Qt 中用于表示字符串的类,实现在 QtCore 共享库中.QStr ...

  5. Java快速入门学习笔记7 | Java语言中的类与对象

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

  6. EJB3.0学习笔记---多接口的时,实现类处理方法:

    EJB学习笔记--- 1.胖客户端:指的是定义的接口太多了,接口做的工作太多; 胖接口: 2.EJB实现类型的定义,用注解的方式,当一个EJBbean,实现了多个接口的时候, 需要用注解的方式指明哪一 ...

  7. Guava学习笔记:简化异常处理的Throwables类

    Guava学习笔记:简化异常处理的Throwables类 参考文章: (1)Guava学习笔记:简化异常处理的Throwables类 (2)https://www.cnblogs.com/peida/ ...

  8. groovy 使用java类_深入学习java中的Groovy 和 Scala 类

    前言 Java 传承的是平台,而不是语言.有超过 200 种语言可以在 JVM 上运行,它们之中不可避免地会有一种语言最终将取代 Java 语言,成为编写 JVM 程序的最佳方式.本系列将探讨三种下一 ...

  9. C++学习笔记-第4单元-对象和类(基础)

    C++学习笔记 文章目录 C++学习笔记 第4单元 对象和类(基础) 单元导读 4.1 用类创建对象 4.1.1 对象和类 4.1.2 创建对象并访问 4.2 对象拷贝.分离声明与实现 4.2.1 对 ...

最新文章

  1. python 连续矫正_Python实现系统时间自动校正 | 学步园
  2. .NET Framework介绍
  3. Elasticsearch Pipeline Aggregation管道聚合详解
  4. 2016年春季计算机应用基础,东北师范2016年春季《计算机应用基础》期末考核
  5. c++11-template template Parameter
  6. 自动化用户特定实体的访问控制
  7. ICDE:POLARDB定义云原生数据库
  8. java 向上抛异常_Java 异常的处理方式throws
  9. LNMP环境SVN钩子脚本的使用
  10. 【Python实例第12讲】谱系共聚类法
  11. The seventeenth day
  12. 前端存储之websql
  13. 微信小程序表格前后台分页
  14. 华为新动作 成立五大“军团”,任正非:没有退路就是胜利之路
  15. 穿山甲android对接错误码40029,头条 穿山甲广告 错误码列表
  16. Android自定义组件之日历控件-精美日历实现(内容、样式可扩展)
  17. 计算机制作幻灯片视频教程,如何在电脑上制作幻灯片?
  18. python什么字体好看_七个不一样的Python代码写法,让你写出一手漂亮的代码
  19. Mysql分表:Merge
  20. 如何进行隐私协议测试

热门文章

  1. iOS开发学无止境 - 异步图片加载优化与常用开源库分析
  2. 最长子段和 11061008 谢子鸣
  3. Asp.net发送邮件的两种方法小结
  4. [zz]volatile
  5. 牛客网(剑指offer) 第十一题 二进制中1的个数
  6. 2018年第九届蓝桥杯 - 省赛 - C/C++大学B组 - G.螺旋折线
  7. 2015年第六届蓝桥杯 - 省赛 - Java大学A组 - A. 熊怪吃核桃
  8. 将SQL文件导入Hive
  9. 一场疫情,炸出了退休的COBOL程序员
  10. 深度学习——02、深度学习入门——经典卷积神经网络架构实例——VGGNet