1、类的行为分类:看起来像一个值;看起来想一个指针。
    1)类的行为像一个值,意味着他应该有自己的状态。当我们拷贝一个像值的对象时,副本和原对象是完全独立的。改变副本不会对原有对象有任何影响,反之亦然。
    2)行为像指针的类则共享状态。当我们拷贝一个这种类的对象时,副本和原对象使用相同的底层数据。改变副本也会改变原对象,反之亦然。
    3)举例:标准库容器和string类行为像一个值;shared_ptr行为类似指针;IO类型和unique_ptr不允许拷贝或者赋值,他们既不像值也不像指针。
    4)我们如何拷贝指针成员决定了我们的类是具有类值行为还是类指针行为。
 
2、例1——定义类值行为的类
class HasPtr {
public:
HasPtr(const string &s = string()) :ps(new string(s)), i() { cout << "这是普通构造函数" << endl; }
//定义析构函数,手动删除指针
~HasPtr() { delete ps; }
//定义拷贝构造函数,拷贝指针指向的对象,而不是指针
HasPtr(const HasPtr &p):ps(new string(*p.ps)),i(p.i){ cout << "这是拷贝构造函数" << endl; }
//定义拷贝赋值运算符,拷贝参数对象的指针的内容,同时把原来的指针删除
HasPtr& operator=(const HasPtr &p);
void setPs(const string &s) { *ps = s; }//改变一下值
string getS()const { return *ps; }
private:
string *ps;
int i;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs) {
auto newp = new string(*rhs.ps);//自赋值的情况下也正确,因为这是底层string的一份拷贝
delete ps;//释放原来指向的内存;
ps = newp;//指向新的内存
i = rhs.i;
cout << "这是拷贝赋值运算符" << endl;
return *this;
}
int main() {
string s = "jjj";
HasPtr *lhs=new HasPtr(s);//使用普通构造函数
HasPtr rhs(*lhs);//使用拷贝构造函数
HasPtr tmp = *lhs;//使用拷贝构造函数
delete lhs;//删除lhs对rhs和tmp没影响
tmp = rhs;//使用拷贝赋值运算符
rhs.setPs("iiii");//改变rhs对tmp没影响
cout <<"rhs="<< rhs.getS() << endl;
cout << "tmp=" << tmp.getS() << endl;
return ;
}
赋值运算符的注意点:
1)如果将一个对象赋予它自身,赋值运算符也必须正常工作。
2)大多数赋值运算符组合了析构函数和拷贝构造函数的工作,换言之,赋值=析构+构造。
3)一个好的方法是在销毁左侧运算对象资源之前,先拷贝右侧运算对象。(如果成员都是值的话无所谓删除与否,如果有指针的话一定要先拷贝后删除,然后再重新赋值,这就是2)的含义)
 
3、例2——定义类指针行为的类
 1)使用shared_ptr,不用定义析构函数,也不需要拷贝构造函数和拷贝赋值运算符
/**
* 拷贝控制与资源管理-定义类指针行为的类
* 首先使用shared_ptr
* 然后自己实现引用计数器
* yzy on 2018-12-20
*/
class HasPtr {
public:
HasPtr(const string &s = string()) :ps(new string(s)), i() { cout << "这是普通构造函数" << endl; }
//不需要拷贝构造函数和拷贝赋值运算符,也不需要析构函数,为了看明白,我们定义一个析构函数
~HasPtr() { cout << "调用了析构函数" << endl; }
void setPs(const string &s) { *ps = s; }
string getPs() { return *ps; }
private:
shared_ptr<string>ps;
int i;
};
int main() {
string s = "iii";
HasPtr *hp1=new HasPtr(s);
HasPtr hp2 = *hp1;//调用合成的拷贝构造函数
HasPtr hp3(*hp1);//调用合成的拷贝构造函数
delete hp1;//删除hp1对hp2和hp3没影响,会调用析构函数,但是ps不会释放
cout << "修改前:hp2="<<hp2.getPs() << endl;
hp3.setPs("jjjjj");//修改hp3会对hp2产生影响
cout << "修改后:hp2=" << hp2.getPs() << endl;
cout << "-------------------------------------" << endl;
HasPtr *new1 = new HasPtr(s);//产生一个新的shared_ptr
hp2 = *new1;//调用合成的拷贝赋值运算符
hp3 = *new1;//调用合成的拷贝赋值运算符,这个时候最开始的shared_ptr应该释放资源了
cout << "hp2=" << hp2.getPs() << " hp3=" << hp3.getPs() << endl;
//手动删除new1
delete new1;
cout << "-----------------------------------" << endl;
return ;
}

2)自己实现引用计数器

/**
* 使用引用计数器,手动实现类似shared_ptr行为,引用计数器工作方式如下:
* 除了初始化对象外,每个构造函数(拷贝构造函数除外)还要创建一个引用计数,用来记录有多少对象与正在创建的对象共享状态。初始化为1
* 拷贝构造函数不分配新的计数器,而是拷贝给定对象的数据成员,包括计数器。拷贝构造函数递增共享的计数器,指出给定对象的状态又被一个新用户所共享
* 析构函数递减计数器,指出共享状态用户少了一个。如果计数器变为0,则析构函数释放状态
* 拷贝赋值运算符递增右侧对象的计数器,递减左侧对象的计数器。如果左侧运算对象的计数器变为0,则意味着它的共享状态没有用户了,那么拷贝赋值运算符就需要销毁状态。、
* 引用计数器要被所有用户共享,所以采用指针的形式
*/
class HasPtr{
HasPtr(const string&s=string()):ps(new string(s)),i(),use(new size_t()){}
//析构函数
~HasPtr() {
--(*use);
if (*use == ) { delete ps; delete use; }
}
//拷贝构造函数
HasPtr(const HasPtr &p) :ps(p.ps), i(p.i), use(p.use) { ++(*use); }
//拷贝赋值运算符
HasPtr& operator=(const HasPtr&);
private:
string *ps;
int i;
size_t *use;
};
HasPtr& HasPtr::operator=(const HasPtr&p) {
++(*p.use);//先加后减,确保自赋值状态下也正确
--(*use);
if (*use == ) {
delete ps;
delete use;//不只要删除ps,还要删除use
}
ps = p.ps;
i = p.i;
use = p.use;
return *this;
}

[C++]类的设计(2)——拷贝控制(拷贝控制和资源管理)的更多相关文章

  1. [C++]类的设计(2)——拷贝控制(阻止拷贝)

    1.阻止拷贝的原因:对于某些类来说,拷贝构造函数和拷贝赋值运算符没有意义.举例:iostream类阻止了拷贝,以避免多个对象写入或者读取相同的IO缓冲.   2.阻止拷贝的方法有两个:新标准中可以将成 ...

  2. [C++]类的设计(2)——拷贝控制(析构和三五法则)

    1.析构函数:释放对象使用的资源,并销毁对象的非static数据成员:析构函数不接受参数,因此不能被重载.对于一个给定类,有且只有一个析构函数.   2.析构函数的组成:一个函数体+一个析构部分(im ...

  3. [C++] 类的设计(2)——拷贝控制(1)

    1.一个类通过定义五种特殊的成员函数来控制此类型对象的拷贝.移动.赋值和销毁:拷贝构造函数.拷贝赋值运算符.移动构造函数.移动赋值运算符和析构函数.(拷贝.移动.析构)   2.拷贝和移动构造函数定义 ...

  4. CPP_类默认函数:构造函数,拷贝构造函数,赋值函数和析构函数

    类默认函数:构造函数,拷贝构造函数,赋值函数和析构函数 // person.h #ifndef _PERSON_H_ #define _PERSON_H_ class Person{ public : ...

  5. [c++基础]3/5原则--拷贝构造函数+拷贝赋值操作符

    /* * main.cpp * * Created on: Apr 7, 2016 * Author: lizhen */ #include <iostream> #include &qu ...

  6. 拷贝构造函数 & 拷贝赋值运算符

    一.拷贝构造函数 1. 形式 class A { public: // ... A(const A &); // 拷贝构造函数 }; 2. 合成拷贝构造函数 编译器总会为我们合成一个拷贝构造函 ...

  7. Unity3D 游戏开发构架篇 ——角色类的设计与持久化

    在游戏开发中,游戏角色占了很大的篇幅,可以说游戏中所有的内容都是由主角所带动.这里就介绍一下角色类的设计和持久化. 一.角色类应用场景和设计思想 游戏中的角色类型不一而足,有不同的技能,有不同的属性等 ...

  8. “乐”动人心--2017年10款最佳音乐类APP设计盘点

    在上下班的路上,听几首自己喜欢的音乐来打发无聊的等公交车和地铁的时间是现代年轻人的常态.音乐作为最能鼓动人心的"语言",也成为了人们在互联网生活里占比例最高的消费活动之一,一款好看 ...

  9. Java SE 之 数据库操作工具类(DBUtil)设计

    JDBC创建数据库基本连接 //1.加载驱动程序 Class.forName(driveName); //2.获得数据库连接 Connection connection = DriverManager ...

随机推荐

  1. 打包Electron项目

    先确保该项目正常运行npm run dev,结束程序, 上篇的项目运行已经提到了打包输出工具electron-packager 可以使用全局安装的方式: npm install -g electron ...

  2. 使用maven搭建ssm框架环境

    1.前言 因为经常换环境,在搭ssm框架的时候老是出错,所以记录一下最近搭建的环境,以供参考. 本文讲解如何使用maven搭建ssm框架,并能用于简单的登录注册. IDE:IDEA,JDK版本:1.8 ...

  3. Redis学习总结(九)-- Redis常用技巧

    这里会记录下Redis 常用的小技巧 全局使用 redis-cli 等命令 在之前我们都在做 Redis 命令目录下通过 ./redis-cli这种形式访问,如果使用 redis-cli 的话就会报命 ...

  4. 栅格数据的批量镶嵌(附Python脚本)

    栅格数据的批量镶嵌(附Python脚本) 博客小序:在数据处理的过程中,会遇到需要大量镶嵌的情况,当数据较多时手动镶嵌较为麻烦,自己最近对分省的DEM数据进行镶嵌,由于利用python进行镶嵌较为方便 ...

  5. 12_goto语句的使用

    1.goto是一个关键字,其作用是运行到goto语句进行跳转,立即执行goto后面所对应标签的语句2.结构:goto 标签名(任意起)3.goto语句尽量不要跨函数使用,否则会使代码看起来非常乱,可读 ...

  6. Springboot学习与mybatis逆向生成工具

    最近H2数据库越用越觉得方便,在不同办公处无缝继续demo的感觉就是爽.   今天接上一篇Springboot简洁整合mybatis,补上sts(即eclipse)使用mybatis generato ...

  7. TK图形界面

    import tkinter 1.使用tkinter模块前 一般先要建立一个tkinter的对象     例: window = tkinter.TK()   2.建立完对象设置好窗口属性以及所有功能 ...

  8. xcode简介及安装

    1. 简介 Xcode 是运行在操作系统Mac OS X上的集成开发工具(IDE),由苹果公司开发. Xcode是开发OS X 和 iOS 应用程序的最快捷的方式. Xcode 具有统一的用户界面设计 ...

  9. STL中的map和multimap小结

    (1)使用map/multimap之前必须包含头文件<map>:#include<map> 并且和所有的关联式容器一样,map/multimap通常以平衡二叉树来完成    ( ...

  10. HDOJ 4253 Two Famous Companies 二分+MST

    题目意思:给出n个点,m条边,边分为两种,一种是A公司的,一种是B公司的.边上有权值, 问用n-1条边把n个点连起来的最小费用是多少,其中A公司的边刚好有k条.题目保证有解. 题解:题目意思很简单就是 ...