muduo1——编程风格:面向对象的编程和基于对象的编程(上)

2023-11-04

muduo库其实不是面向对象的编程,而是基于对象的编程,那么在进入正式的muduo源码分析之前,先来看看这两种编程风格

一、面向对象编程风格

通过对一个线程类的封装来进行讲解:
在这里插入图片描述
Thread是一个抽象类不能实例化对象,TestThread是派生类一个具体类;每一种线程类是有一个自己的执行体,用Run表示,不同的线程类其执行体是不一样的,就将这个方法提升到基类做一个抽象的接口

代码简单示例, 重点分析在下面专门讲

/*Thread.h*/
#ifndef _THREAD_H_
#define _THREAD_H_

#include<pthread.h>

class Thread
{
public:
	Thread();
	virtual ~Thread();//要纯虚哦
	
	void Start();
	void Join();
private:
	static void* ThreadRoutine(void* arg);//不能直接在创建线程时把run作为函数入口, 用这个静态函数开个路
	virtual void Run() = 0;	//纯虚函数
	pthread_t threadId_;
};
#endif //_THREAD_H_
/*Thread.cpp*/
#include "Thread.h"
#include<iostream>
using namespace std;

Thread::Thread()
{
//...
}
Thread::~Thread()
{
//...
}
void Thread::Start()
{
	
	//第三个参数就是线程的入口函数,但是注意不能直接把Run()放进来
	pthread_create(&threadId_, NULL, ThreadRoutine, this);
	//那这边我们所创建的线程的线程入口函数是ThreadRoutine,并不是我们真正要执行的run, 因此我们要在ThreadRoutine里调用run方法
}
void Thread::Join()
{
	pthread_join(threadId_, NULL);
}
//
void* Thread::ThreadRoutine(void* arg)//静态成员函数
{
	//Run();//但也不能直接调用啦, 因为ThreadRoutine是静态成员函数,不能调用非静态的
	//可以靠传递过来的this⭐妙
	Thread* thread = static_cats<Thread*>(arg);//this指针转换成基类指针,
	//到时候this指针一定是一个派生类对象的指针,现在将基类指针指向派生类对象
	//并且调用Run函数
	thread->Run();
	return NULL;
}

定义一个具体类实现run接口(派生类,去实现执行体run()

/*thread_tset.cpp*/
#include "Thread.h"
#include<iostream>
#include<unistd.h>
#include<unistd.h>
using namespace std;

class TestThread : public Thread//继承
{
public:
	TestThread(int count) : count_(count)
	{
		cout<<"Test..."<<endl;
	}	
	~TestThread()
	{
		cout<<"~TestThread..."<<endl;
	}
	
	viod Run()//实现run方法,这个抽象的方法
	{
		//假设在函数中打印输出,那么会希望它传递一个count参数
		//虽然方法没有参数,但是这个类可以有一些状态, 相当于成员变量啦
		while(count_--)
		{
			cout<<"this is a test..."<<endl;
			sleep(1);//睡眠函数<unistd.h>
		}
	}//这就是实际线程的执行体函数
	
	int count_;//成员变量还可以有多个,就相当于成员变量的参数了,反正可以访问,在构造函数中传进去并初始化就ok
};

int main(void)
{
	//不能直接使用基类,要使用派生类实例化一个对象
	TestThread t(5);
	t.Start();
	t.Join();//避免主线程结束了子线程还没有运行的情况,主线程进入等待池
	
	return 0;
}

另外需要注意pthread的运行需要去链接线程库,在cmake中,要target_link_libraries(Thread_test_pthread),链接这个库

为什么不能直接把Run函数作为线程的入口函数

因为Run是普通的成员函数,隐含的第一个参数是Thread* (this),即会把this指针传递进来,调用的时候是thiscall约定,而thiscall约定呢,某一个寄存器会保存this指针,而int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg);----> (传出一个线程的id,线程的属性默认属性就用空指针,线程的入口函数,参数)
*start_routine是一个普通的函数调用约定, 所以run函数不能作为它的入口函数(?),
解决方法:编写一个这种接口的void *(*start_routine)(void*)全局的函数肯定可以解决,但是全局的话很多人都看的到,所以还是作为一个成员函数ThreadRoutine, 加一个静态,这样函数就没有一个隐含的this指针了,在调用的时候就不会传递一个隐含的this指针,
那另一个问题就是线程函数的参数要怎么传递过来:就用this指针,指向当前第项自身的指针

关于thiscall:thiscall不是关键字。它是C++类成员函数缺省的调用约定,即如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入堆栈;


总结这部分代码:(虚函数多态的体现

⭐一个派生类对象t,调用t.Start(),t的第一个参数隐含的是一个this指针,相当于就是取地址t,&t
Start函数中又调用ThreadRoutine,参数是this指针指向对象自身,也就是指向了取地址t,即ThreadRoutine函数中的参数arg就是&t, 第一行代码Thread* thread = static_cast<Thread*>(arg)就成功完成了“基类指针指向派生类对象”,然后再通过基类指针调用的应该是派生类所实现的虚函数;这使用到了虚函数的多态

那么如果线程类是库实现的, 现在创建了一个线程就相当于是线程库里面回调了Run方法,基类指针指向了派生类对象,调用的是派生类的Run接口
也就是说虚函数具有回调的功能

为什么要把Run()放在private:

❗❗❗❗❗
如果run没有做成private, 然后直接创建实例对象t,然后跑t.Run(),可以跑起来,但是t.Run() 本质上还是在主线程中执行,并不能够满足“是创建了一个线程,成为一个线程的执行体函数”,这不算一个线程, 所以一定要调用Start,内部才会创建一个线程,让线程回调这个执行体函数,
所以,不应该直接调用它,所以就不可以把它放到public中

线程对象的生命周期与线程的生命周期

这两者是不一样的,线程执行完毕线程对象不一定被销毁, 比如:在线程执行完之后,如果再sleep10秒,那么线程对象其实在这个时间里其实还是存在的,线程对象要等程序运行完才会销毁

但还是可以实现线程执行完毕,线程对象自动销毁:
:那就要delete去销毁, 那么必须要动态创建对象才可以

可以在.h中增加一个属性:bool autoDelete_;是否自动销毁,构造函数的时候也相应的要去初始化这个属性, 对应增加一个函数void SetAutoDelete(bool autoDelete);

int main(void)
{
	TestThread* t2 = new TestThread(5);
	t2->SetAutoDelete(true);//在start之前定义一下
	t2->Start();
	t2->Join();	
	for(; ;)
		pause();//线程结束,但线程对象还没有结束
	return 0;
}
void Thread::SetAutoDelete(bool autoDelete)
{
	autoDelete_ = autoDelete;
}
void* Thread::ThreadRoutine(void* arg)
{
	Thread* thread = statiic_cast<Thread*>(arg);
	thread->Run();
	if(thread->autoDelete_)
	{
		delete thread;//这下,当线程的执行体Run执行完毕,就可以把线程对象delete销毁掉了
	}
	return NULL;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

muduo1——编程风格:面向对象的编程和基于对象的编程(上) 的相关文章

  • R语言与面向对象的编程(3):R6类

    专注系列化 高质量的R语言教程 本号已支持快捷转载 无需白名单即可转载 本系列将介绍R语言中三个与面向对象的编程 Object Oriented Programming OOP 相关的工具包 proto R6和基础包methods 这是一个
  • 【最清晰】ThreadLocal和局部变量和成员变量的区别

    ThreadLocal是进程级别的全局变量 最近有一个疑惑 为什么线程类的局部变量不能完全替代ThreadLocal 每一次new 线程都是创建了一个副本啊照理来说也是独立的 为什么还需要ThreadLocal 实际上确实是独立的 但是答案
  • JAVA的三大特征之多态

    多态 什么是多态 多态就是同一个行为的不同表现形式 换句话说就是同一个方法因为对象的不同所产生不同的结果 多态存在的条件 继承 重写 父类引用指向子类对象 例 public static void main String args a是人的
  • Java知识点汇总--多态

    Java多态 1 多态 1 1 多态的概述 1 2 多态中的成员访问特点 1 3 多态的好处和弊端 1 4 多态中的转型 1 5 多态的案例 1 多态 1 1 多态的概述 什么是多态 同一个对象 在不同时刻表现出来的不同形态 多态的前提 要
  • 什么是多态?如何实现?只看这一篇就够了

    1 多态的概念 1 1 概念 多态的概念 通俗来说 就是多种形态 具体点就是去完成某个行为 当不同的对象去完成时会产生出不同的状态 2 多态的定义及实现 2 1多态的构成条件 多态是在不同继承关系的类对象 去调用同一函数 产生了不同的行为
  • Java继承和多态之接口

    Java继承和多态之接口 题目要求 仔细阅读右侧编辑区内给出的代码框架及注释 在 Begin End 中实现两个数的求和运算和比较 具体要求如下 编写程序 实现两个数的求和运算和比较 请在下面的Begin End之间按照注释中给出的提示编写
  • 同步方法及同步代码块

    synchronized方法和synchronized块 synchronized方法必须获得对象的锁才能执行 否则线程阻塞 方法一旦执行 就独占此锁 直到方法返回才释放锁 后面被阻塞的线程才能获得这个锁 继续执行 synchronized
  • c++面向对象三大特征封装、继承和多态知识总结

    面向对象三大特征 封装 继承 多态 一 封装 该公开的就公开话 该私有的就隐藏掉 主要是由public private实现 作用是便于分工和分模块 防止不必要的扩展 二 继承 就是一种传承 可以把父类型中的数据传承到子类中 子类除了传承了父
  • 面向对象的编程思想和Python的类,访问和属性,继承

    面向对象的编程思想和Python的类 类的方法和属性 实例方法 这一文从面相对象的角度 介绍类的定义 类的属性和自定义方法 本文将从访问限制 属性 继承 方法重写这几个方面继续介绍面向对象的编程思想和Python类的继承 一 访问权限 Py
  • 面向对象的编程思想和Python的继承和多态,特殊方法,引用计数

    面向对象的编程思想和Python的类 访问和属性 继承 在上一文中我们了解到了 私有的属性的访问方式 实例名 类名 私有属性名 一 私有的属性如何对外提供公有的取值和赋值方法呢 提供公有的方法作为接口进行取值 例如 class Avg Sc
  • python_os.walk(dir)

    for root dirs files in os walk dir os walk返回一个三元组 path 对当前路径以及其下所有的子目录进行递归 dirs 当前路径下的子目录 files 当前路径下的文件 gt gt gt for r
  • C++多态概念和意义

    目录 一 什么叫重写 二 面向对象期望的重写 1 示例分析 2 所期望的重写 三 多态的概念和意义 1 多态的概念 2 C 如何支持多态概念 3 多态内部运行剖析 4 多态的意义 5 修改示例代码 四 静态联编和动态联编 五 小结 一 什么
  • log4net使用

    说明 本程序演示如何利用log4net记录程序日志信息 log4net是一个功能著名的开源日志记录组件 利用log4net可以方便地将日志信息记录到文件 控制台 Windows事件日志和数据库 包括MS SQL Server Access
  • C++学习笔记5:继承、多态基础

    组合 继承 继承与组合 构造与析构顺序 派生类重载基类函数的访问 多态性 虚函数 纯虚函数和抽象基类 多重继承 派生类成员的标识与访问 组合 继承与多态性 面向对象涉及的重要目的之一就是代码重用 多态性可以以常规方式书写程序来访问多种现有的
  • Java线程的5种状态及状态之间转换

    Java中的线程的生命周期大体可分为5种状态 1 新建 NEW 新创建了一个线程对象 2 可运行 RUNNABLE 线程对象创建后 其他线程 比如main线程 调用了该对象的start 方法 该状态的线程位于可运行线程池中 等待被线程调度选
  • CreateRemoteThread的使用(转载)

    先解释一下远程进程 其实就是要植入你的代码的进程 相对于你的工作进程 如果叫本地进程的话 它就叫远程进程 可理解为宿主 首先介绍一下我们的主要工具CreateRemoteThread 这里先将函数原型简单介绍以下 CreateRemoteT
  • 多线程的异步调用(一)

    最近手头做的项目中 用到了多线程的异步调用 在控制线程中实时的检测硬件的变化 如果硬件发生了某些变化 那么需要通知别的模块做一些相应的操作 为了让这些操作不会影响控制线程的继续运行 就在多线程中使用了异步调用的方法 using System
  • 多核编程学习笔记之OpenMP(一)

    多核编程学习笔记之OpenMP 一 I 配置及简介 1 1 在VC 2008 VC9 0 中 如果没有任何设置 在代码中使用编译指导语句将不会报错 但是也不起作用 1 2 OpenMP发展与优势 1 2 1 OpemMP的规范由SGI发起
  • 友元成员函数使用时的注意事项

    友元成员函数的注意事项 友元的概念 友元 的概念其实是针对于类的私有成员来说的 一个类的由于封装的缘故 类体中私有成员是不可以被外界访问的 无论是继承也好 都是无法访问类内私有成员的 但是正是有那么一些人 愿意打破陈规破例访问类体内的私有成
  • Java并发编程:CountDownLatch、CyclicBarrier和 Semaphore

    Java并发编程 CountDownLatch CyclicBarrier和 Semaphore 2016 10 07 分类 基础技术 7 条评论 标签 并发 分享到 0 原文出处 海子 在java 1 5中 提供了一些非常有用的辅助类来帮

随机推荐

  • telnet mysql3306端口失败

    在linux上telnet远程mysql端口失败 经过上网查找后 找到多种方法 1 我在本地的Navicat上新增了一个用户 主机名是linux的ip 也可以是 百分号代表这个用户可以在任何地方对mysql进行远程连接 2 登录mysql
  • mipsel-openwrt-linux交叉编译zlog日志库并测试

    mipsel openwrt linux交叉编译zlog日志库并测试 文章目录 mipsel openwrt linux交叉编译zlog日志库并测试 一 准备 二 交叉编译测试 1 mipsel openwrt linux交叉编译过程 ar
  • 机器学习集成模型学习——Bagging集成学习(三)

    Bagging bagging的集成方式是 用1个模型 元模型 然后将这个元模型分成多个相同模型 每个模型使用训练集的一部分进行训练 得到多个基模型 最后测试时分别跑每个模型 平均结果得出这个集成模型的最终预测结果 案例代码 from sk
  • VSCode将QT(shadow build)编译输出到指定文件

    VSCode将QT shadow build 编译输出到指定文件 一 啥是shadow build 实际上就是将编译输出的文件跟源码文件放在不同地方 也就是out of source 0 00 在qtcreator中设置就很方便很方便 直接
  • C#将字符串格式化为Json

    private string ConvertStringToJson string str 格式化json字符串 JsonSerializer serializer new JsonSerializer TextReader tr new
  • 如何判断Javascript对象是否存在

    Javascript语言的设计不够严谨 很多地方一不小心就会出错 举例来说 请考虑以下情况 现在 我们要判断一个全局对象myObj是否存在 如果不存在 就对它进行声明 用自然语言描述的算法如下 if myObj不存在 声明myObj 你可能
  • 机器学习总结之第二章模型评估与选择

    2 1经验误差与过拟合 错误率 a个样本分类错误 m个样本 精度 1 错误率 误差 学习器实际预测输出与样本的真是输出之间的差异 训练误差 即经验误差 学习器在训练集上的误差 泛化误差 学习器在新样本上的误差 过拟合 学习器把训练样本学的
  • PHP自增、自减运算流程解析

    PHP自增运算解析 0x01 PHP自增运算 代码如下 0x02 PHP自减运算 代码如下 0x01 PHP自增运算 代码如下
  • Java判断一个时间是否在时间区间内

    package com liying tiger test import java text ParseException import java text SimpleDateFormat import java util Calenda
  • 图像处理——滤波器的比较

    滤波器 方框滤波 boxFilter 均值滤波 blur 高斯滤波 GaussianBlur 中值滤波 medianBlur 线性滤波器 线性滤波器经常用于剔除输入信号中不想要的频率或者从许多频率中选择一个想要的频率 常见的有 低通 高通
  • linux命令 chmod 755的含义 及drwxr-xr-x 的含义

    linux drwxr xr x 第一位表示文件类型 d是目录文件 l是链接文件 是普通文件 p是管道 第2 4位表示这个文件的属主拥有的权限 r是读 w是写 x是执行 第5 7位表示和这个文件属主所在同一个组的用户所具有的权限 第8 10
  • docker 安装node

    docker 安装node 1 使用docker安装node 使用docker安装 docker pull node 拉取镜像 docker run id name c node node 创建容器 可以看到已经装好了 启动node doc
  • Linux系统编程-C++ I/O库

    文章目录 一 总述 二 输出缓冲 三 文件输入输出 四 string流 五 输入输出格式 总述 1 控制布尔值的格式 2 指定整型值的进制 3 在输出中指出进制 4 控制浮点数格式 4 1 指定打印精度 5 输出空白 六 未格式化的输入输出
  • java 数组中存储26个英文字母_利用数组打印26个英文字母

    可以考虑下面两种程序 1 public class Letter public static void main String args for char c a c lt z c System out print c 2 class AB
  • 如何实现一个定时器?看这一篇就够了

    本文主要介绍定时器作用 实现定时器数据结构选取 并详细介绍了跳表 红黑树 时间轮实现定时器的思路和方法 定时器作用 定时器在各种场景都需要用到 比如游戏的Buff实现 Redis中的过期任务 Linux中的定时任务等等 顾名思义 定时器的主
  • Qt常用部件介绍

    这里先给大家介绍 Designer 界面设计器 中例举的常用部件 以便对 Qt 的部件有一定认识 其具体用法后面再作介绍 布局管理组 Layouts 空间间隔组 弹簧 Spacers 按钮组 buttons 项目视图组 Item Views
  • 设计模式深入浅出--21.命令模式简单实例及其在JDK中的应用

    命令模式 定义 将 请求 封装成对象 以便使用不同的请求 命令模式解决了应用程序中对象的职责以及它们之间的通信方式 类型 行为型 适用场景 请求调用者和请求接收者需要解耦 使得调用者和接收者不直接交互 需要抽象出等待执行的行为 优点 降低解
  • 数据表格(QTableWidget)

    一 简介 QTableWidget是QT对话框设计中常用的显示数据表格的控件 QTableWidget单元格数据是QTableWidgetItem对象来实现的 整个表格都需要用逐个单元格对象QTableWidgetItem构建起来 二 详解
  • Presto 与 Hive 语法学习

    Presto 与 Hive 语法学习 文章目录 Presto 与 Hive 语法学习 1 Presto语法 1 1 数据类型 布尔值 整数 浮点 固定精度 字符串 日期和时间 结构 网络地址 UUID HyperLogLog KHyperL
  • muduo1——编程风格:面向对象的编程和基于对象的编程(上)

    muduo库其实不是面向对象的编程 而是基于对象的编程 那么在进入正式的muduo源码分析之前 先来看看这两种编程风格 一 面向对象编程风格 通过对一个线程类的封装来进行讲解 Thread是一个抽象类不能实例化对象 TestThread是派