C++程序练习
本程序是从工作中一个项目里得到的灵感,借鉴了项目里的设计思路。本程序只用到了多线程,多态性、C++标准
容器、C++随机数生成引擎、模版类、模版函数、宏定义函数以及system系统调用等等。
个人感觉还是有质量的,看1遍是不可能有人能看出来个意思的(如果不看我写的程序结构的话),呃呃呃。
程序结构
-
DesData:目标数据类。其有5个属性: int sonage; int sonmoney; int dauage; bool isdaubea; int daumoney;(由于种种原因导致属性名称显得很是奇怪,因为是记录,就懒得改了),为了方便操作,只设置了int和bool类型。
-
Adapter< T > 类:一个抽象基类,作为操作实体类Son,BeaAgeM的接口,声明一些操作函数,如Parse()虚函数,目的在于接受模拟数据并对其解析,UpdateData()为了及时更新发送数据,保证数据及时更新。因为是案例,全都把本类的函数设置成了纯虚函数,也就是其子类必须全部重写这些函数,即子类都具有数据的发送和接收的功能。
-
Son:主要模拟接收和发送目标数据类DesData的sonage以及sonmoney属性。
-
Daughter:主要模拟接收和发送目标数据类DesData的dauage、isdaubea以及daumoney属性。
-
Manager< T >:可看做一个的管理数据的容器。主要有三个成员属性:
a. unordered_map<uint32_t, unique_ptr<Adapter > > _recvmap; 保存接收数据的实体类指针。
b. unordered_map<uint32_t, unique_ptr<Adapter > > _sendmap; 保存发送数据的实体类指针。
c. T mydata; 只要操作的目标数据实体。 -
RecvData:用于不停接受模拟数据的接收线程。
-
SendData: 用于不停更新并发送数据的发送线程。
-
SendFrame: 用于更新发送数据的接口类。主要有三个属性:Adapter* pdata = nullptr; 及时获取数据变化,绑定于TestData中建立的用于发送数据的实体类指针;Frame data_update;用于把更新内容保存下来,从pdata取值; Frame data_send; 用于发送出去的数据,从data_update取值。
-
SensorT< T >: 用于把管理类Manager、RecvData以及SendData结合接口。实现初始化以及启动接收和发送线程,也同时把建立具体对象实体和对象共有的操作分开,使代码更简洁。
-
TestData:SensorT的子类,主要建立并初始化Son以及Daughter等具体的数据类实体。通过Manage的AddRecv< T >()以及AddSend< T >()方法,为数据类Son,Daughter分配内存,并用unique_ptr管理其生命周期,以各自的id为键存入Manager里对应的Map里。 这里注意RegisterSend()方法,对于需要发送的数据类,必须建立一个该对象指针,并调用RegisterSend()函数,一是用于初始化该指针(通过类的指定ID,指向Manager的Map里找到其对应的一块内存),二是初始化SendData的vsendlist_里的发送对象实体SendFrame,把其中的pdata指针与其绑定,以便及时更新要发送的数据。总结起来,对于发送,一共有三处指针指向同一块内存空间, 起初是调用AddSend()方法时,在Manager的sendMap里初始化了一块内存记为M,然后在调用RegisterSend()方法后,使得TestData类中的用于处理发送数据的指针(记为TP)初始化指向了M内存空间,最后,把SendFrame的pdata指针与TestData类TP*绑定也指向M内存空间。*
-
TMain:主要看下面两个线程:一个HandleRecvThread线程,一个UpdateSendThread线程,此处的HandleRecvThread线程只是把从后台RecvData里的接收线程已经收到的DesData数据,以一定周期显示出来。UpdateSendThread线程则是把从后台收到的DesData数据,作为新数据,以一定周期更新发送数据,后台SendData里的发送线程则会一直以指定的周期发送数据。为了保证数据一致性,数据的更新周期要小于数据的发送周期,本程序是缩小了10倍。
程序运行效果
在此借用了”boxes“显示输出工具,所以要先使用"sudo apt-get install boxes"命令,安装boxes,再运行程序。
在此借用了”xcowsay“显示输出工具,所以要先使用"sudo apt-get install xcowsay"命令,安装boxes,
并更改程序开头的“SYSDISPALYFUN”字段为"xcowsay",再运行程序。
编译命令
在程序当前目录使用:g++ main.cpp -std=c++11 -lpthread -o main
运行 : ./main
程序代码
为了阅读和书写方便把所有代码只写在了同一个cpp里以及方法的实现都是类内实现。
注意:由于打印输出用到了系统调用,所以运行本程序之前,需要在Linux系统上安装"boxes",或者其他
可用管道同步输出的工具,比如“cowsay"或者 "xcowsay"、“toilet”、”figlet“等等, 然后替换
程序开头的“SYSDISPALYFUN”字段即可。本程序默认使用”boxes“显示,推荐试下"xcowsay",不要太有趣~!
/******************************************************************************
* Complete by Solitary_Tang(Tang xiao long) at 2021-3-10 11:05.
* Destination: just regard as a exersice.
* Language: C++
* System: Linux
* Tips: May be, you should install "boxes" and "xcowsay" before run this code.
* CSND blog link: https://blog.csdn.net/qq_37174816/article/details/114633330
*****************************************************************************/
#include <iostream>
#include <vector>
#include <memory>
#include <iomanip>
#include <cstring>
#include <unordered_map>
#include <thread>
#include <bitset>
#include <random>
#include <mutex>
#include <chrono>
#include "util.h"
#define MAX_DATA_LEN 32
using namespace std;
using namespace std::chrono;
using Timepointer = std::chrono::system_clock::time_point;
std::mutex m_recv_data;
std::mutex m_send_data;
/**
* @brief delta_period
* thread delay time
* default is 100ms
*/
uint32_t delta_period = 100;
/**
* @brief SYSDISPALYFUN
* Dispaly info with the command named "boxes",
* you can change it to "xcowsay" for example.
*/
string SYSDISPALYFUN = "boxes";
struct Frame{
uint32_t ID;
uint8_t len;
uint32_t data[3];
Frame():ID(0),len(0)
{
std::memset(data, 0, sizeof(data));
}
};
struct realData
{
__uint32_t id;
__uint8_t dlc;
uint32_t data[3];
};
/**
* @brief The DesData class
* Main data class
*/
class DesData
{
public:
DesData() = default;
// DesData(DesData&& fa):age(std::move(fa.age)),money(std::move(fa.money)){}
int getSonmoney() const
{
return sonmoney;
}
void setSonmoney(int value)
{
this->sonmoney = value;
}
int getDauage() const
{
return dauage;
}
void setDauage(int value)
{
this->dauage = value;
}
bool getIsdaubea() const
{
return isdaubea;
}
void setIsdaubea(bool value)
{
this->isdaubea = value;
}
int getDaumoney() const
{
return daumoney;
}
void setDaumoney(int value)
{
this->daumoney = value;
}
int getSonage() const
{
return sonage;
}
void setSonage(int value)
{
this->sonage = value;
}
void console() const
{
string strinfo = "\necho '";
strinfo += "RecvData:\n";
strinfo += "Sonage\tSonmoney($)\tdauage\tdaumoney($)\tDbeautiful\n";
strinfo += to_string(sonage)+"\t"+to_string(sonmoney)+"\t\t";
strinfo += to_string(dauage)+"\t"+to_string(daumoney)+"\t\t";
strinfo += (isdaubea>0) ? "true\t\t' |"+SYSDISPALYFUN : "false\t\t' | "+SYSDISPALYFUN;
system( strinfo.c_str() );
}
private:
int sonage;
int sonmoney;
int dauage;
bool isdaubea = false;
int daumoney;
};
/**
* @brief The Adapter class
* The basic class of class order to Recv or Send elements
*/
template <typename SensorType>
class Adapter
{
public:
Adapter() = default;
virtual void print() const = 0;
virtual uint8_t GetLen() const = 0;
virtual uint32_t GetId() const = 0;
virtual void Parse(const uint32_t* , SensorType* ) const = 0;
virtual void UpdateData(uint32_t* ) =0;
};
class Son : public Adapter<DesData>
{
public:
static const uint32_t ID ;
Son() = default;
Son(int age, int money)
{
this->age = age;
this->money = money;
}
void print() const override
{
string strinfo = "\necho '";
strinfo += "SendData:\n";
strinfo += "Sonid\tSonage\tSonmoney($)\t\t\t\n";
strinfo += to_string(ID)+" \t"+to_string(age)+" \t";
strinfo += to_string(money)+"\t' | "+SYSDISPALYFUN;
system( strinfo.c_str() );
}
void setAge(int a)
{
age = a;
}
void setMoney(int m)
{
money = m;
}
void setLen(uint8_t len)
{
this->len = len;
}
uint8_t GetLen() const override
{
return len;
}
uint32_t GetId() const override
{
return ID;
}
void Parse(const uint32_t* data, DesData* fa) const override
{
fa->setSonage(static_cast<int>(data[0]));
fa->setSonmoney(static_cast<int>(data[1]));
}
void UpdateData(uint32_t* des) override
{
struct Temp{
int age;
int money;
};
Temp t = {age, money};
memcpy(des, &t, sizeof(Temp));
}
private:
uint8_t len = 8;
int age;
int money;
};
class Daughter : public Adapter<DesData>
{
public:
static const uint32_t ID;
Daughter() = default;
Daughter(int age)
{
this->age = age;
}
Daughter(int age, bool beautiful, int money):beautiful(beautiful)
{
this->age = age;
this->money = money;
}
void print() const override
{
string strinfo = "\necho '";
strinfo += "SendData:\n";
strinfo += "Dauid\tDauage\tDauisBeautiful\tDaumoney($)\t\t\t\n";
strinfo += to_string(ID)+" \t"+to_string(age)+" \t";
strinfo += (beautiful>0) ? "true\t\t" : "false\t\t";
strinfo += to_string(money)+"\t' | "+SYSDISPALYFUN;
system( strinfo.c_str() );
}
void setAge(int a)
{
age = a;
}
void setbeautiful(bool b)
{
beautiful = b;
}
void setMoney(int m)
{
money = m;
}
uint8_t GetLen() const override
{
return len;
}
uint32_t GetId() const override
{
return ID;
}
void Parse(const uint32_t* data, DesData* fa) const override
{
fa->setDauage(data[0]);
fa->setIsdaubea(static_cast<bool>(data[1]));
fa->setDaumoney(data[2]);
}
void UpdateData(uint32_t* des) override
{
des[0] = age;
des[1] = static_cast<uint32_t>(beautiful);
des[2] = money;
}
private:
uint8_t len = 12;
int age = 0;
bool beautiful = false;
int money = 0;
};
const uint32_t Son::ID = 0x01;
const uint32_t Daughter::ID = 0x02;
/**
* @brief The Manager class
* You can regard it as a vector to reserve Adapter-type pointer.
*/
template <typename T>
class Manager
{
private:
unordered_map<uint32_t, unique_ptr<Adapter<T> > > _recvmap;
unordered_map<uint32_t, unique_ptr<Adapter<T> > > _sendmap;
T mydata;
public:
Manager() = default;
~Manager(){cout<<"~Manager()"<<endl;}
template<typename S>
inline bool Addrecv()
{
_recvmap[S::ID] = make_unique<S>();
return true;
}
template<typename S>
inline bool Addsend()
{
_sendmap[S::ID] = make_unique<S>();
return true;
}
Adapter<T>* GetMutableAdapterById(const uint32_t id)
{
if(_sendmap.find(id) == _sendmap.end())
return nullptr;
return _sendmap[id].get();
}
void Parse(const uint32_t* data, const uint32_t id)
{
const auto& it = _recvmap.find(id);
if(it == _recvmap.end())
{
cout << "Recv Error Id is "<<id<<endl;
}else
{
std::lock_guard<std::mutex> lock(m_recv_data);
it->second.get()->Parse(data, &mydata);
}
}
void GetData(T* p) const
{
std::lock_guard<std::mutex> lock(m_recv_data);
*p = mydata;
}
};
/**
* @brief The RecvData class
* A thread-class to receive the simulate data.
*/
template<typename T>
class RecvData
{
private:
Manager<T>* recvmanager_;
unique_ptr<thread> thread_;
bool isrunning = false;
public:
RecvData() = default;
~RecvData()
{
StopRecv();
}
bool init(Manager<T>* sh)
{
if(nullptr == sh)
ERROR("init RecvData manager pointer Failed!");
recvmanager_ = sh;
return true;
}
void StopRecv()
{
if(isrunning)
{
std::cout << "stop recv data thread begin...." << std::endl;
isrunning = false;
if(thread_ != nullptr && thread_->joinable())
thread_->join();
thread_.reset();
std::cout << "stop recv data successfully end" << std::endl;
}else
std::cout << "the recv data thread has stoped!" << std::endl;
}
void SimulateRecv(Frame& frame)
{
realData realdata;
static default_random_engine e(time(NULL));
uniform_int_distribution<unsigned int> uid(1,2);
uniform_int_distribution<int> uage(10,50);
uniform_int_distribution<int> umoney(100000,9000000);
realdata.id = uid(e);
if(1 == realdata.id)
{
realdata.data[0] = uage(e);
realdata.data[1] = umoney(e);
realdata.dlc = 8;
}else if(2 == realdata.id)
{
bernoulli_distribution b;
realdata.data[0] = uage(e);
realdata.data[1] = b(e);
realdata.data[2] = umoney(e);
realdata.dlc = 12;
}
frame.ID = realdata.id;
frame.len = realdata.dlc;
memcpy(frame.data, realdata.data, frame.len);
}
void HandleRecvThread()
{
Timepointer tm_start;
Timepointer tm_end;
uint64_t sleep_interval = 0;
while(isrunning)
{
tm_start = std::chrono::system_clock::now();
Frame frame;
SimulateRecv(frame);
recvmanager_->Parse(frame.data, frame.ID);
tm_end = std::chrono::system_clock::now();
sleep_interval = delta_period -
(std::chrono::duration_cast<std::chrono::milliseconds>
(tm_end - tm_start).count())/1e6;
if(sleep_interval > 0)
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_interval));
}
}
bool StartRecv()
{
if(isrunning)
ERROR("recv data thread is running!");
isrunning = true;
thread_.reset(new thread(
[this]
{
HandleRecvThread();
}));
return true;
}
};
template <typename T>
class SendFrame
{
public:
SendFrame() = default;
~SendFrame() {cout<<"~SendFrame()"<<endl;}
SendFrame(uint32_t id, Adapter<T>* pt)
{
if(nullptr == pt)
cout<<"Error!!! try initial 'pdata' (Adapter<T>*)of SendFrame with a null pointer"
<<endl;
data_update.ID = id;
data_update.len = pt->GetLen();
pdata = pt;
Update();
}
bool Update()
{
if(nullptr == pdata)
{
string errorinfo = "Update failed! Cuz the data pointer is null! id = ";
errorinfo.append(to_string(data_update.ID));
ERROR(errorinfo);
}
pdata->UpdateData(data_update.data);
std::lock_guard<mutex> lock(m_send_data);
data_send = data_update;
return true;
}
Frame GetSendData() const
{
std::lock_guard<mutex> lock(m_send_data);
return data_send;
}
private:
Frame data_update;
Frame data_send;
Adapter<T>* pdata = nullptr;
};
/**
* @brief The SendData class
* A thread-class to send the data that received by RecvData class.
*/
template<typename T>
class SendData
{
public:
SendData() = default;
~SendData()
{
StopSend();
}
bool AddSendData(const uint32_t id, Adapter<T>* p)
{
if(nullptr == p)
ERROR("Invailed data pointer!");
vsendlist_.emplace_back(SendFrame<T>(id, p));
return true;
}
void Update()
{
for(auto &i : vsendlist_)
{
i.Update();
}
}
bool init(Manager<T>* sh)
{
if(nullptr == sh)
ERROR("init SendData manager pointer Failed!");
sendmanager_ = sh;
return true;
}
void StopSend()
{
if(isrunning)
{
std::cout << "stop send data thread begin...." << std::endl;
isrunning = false;
if(thread_ != nullptr && thread_->joinable())
thread_->join();
thread_.reset();
std::cout << "stop send data successfully end" << std::endl;
}else
std::cout << "the send data thread has stoped!" << std::endl;
}
bool StartSend()
{
if(isrunning)
ERROR("The send thread has running!");
isrunning = true;
thread_.reset(new thread(
[this]
{
HandleSendThread();
}
));
return true;
}
bool SendSingleFrame(Frame frame)
{
Son Sonre;
Daughter Daughterre;
realData real;
memcpy(&real, &frame, sizeof(frame));
switch (frame.ID) {
case 1:
Sonre = Son(static_cast<int>(real.data[0]), static_cast<int>(real.data[1]));
Sonre.print();
break;
case 2:
Daughterre = Daughter(static_cast<int>(real.data[0]),static_cast<bool>(real.data[1]),
static_cast<int>(real.data[2]));
Daughterre.print();
break;
default:
ERROR("Data id is not found! id = "+to_string(frame.ID));
break;
}
return true;
}
void HandleSendThread()
{
Timepointer tm_start;
Timepointer tm_end;
uint64_t sleep_inteval = 0;
while(isrunning)
{
tm_start = std::chrono::system_clock::now();
for(auto &item : vsendlist_)
{
SendSingleFrame(item.GetSendData());
}
tm_end = std::chrono::system_clock::now();
sleep_inteval = delta_period -
(std::chrono::duration_cast<std::chrono::milliseconds>
(tm_end - tm_start).count())/1e6;
if(sleep_inteval > 0)
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_inteval));
}
}
private:
Manager<T>* sendmanager_;
unique_ptr<thread> thread_;
bool isrunning = false;
vector<SendFrame<T> > vsendlist_;
};
/**
* @brief The SensorT class
* Combine the Manager, RecvData and SendData class.
* Regard as a interface
*/
template<typename T>
class SensorT
{
public:
SensorT() = default;
~SensorT(){cout<<"~SensorT()"<<endl;}
bool Init()
{
sensorManager_ = make_unique< Manager<T> >();
senddata.init(sensorManager_.get());
recvdata.init(sensorManager_.get());
return true;
}
bool Start()
{
if(!recvdata.StartRecv())
ERROR("start recv thread Failed!");
if(!senddata.StartSend())
ERROR("start recv thread Failed!");
return true;
}
template<typename S>
bool RegisterSend(S*& sh)
{
sh = (dynamic_cast<S*>(sensorManager_->GetMutableAdapterById(S::ID)));
if(nullptr == sh)
ERROR("RegisterSend Failed!");
senddata.AddSendData(S::ID, sh);
return true;
}
unique_ptr<Manager<T>> sensorManager_;
RecvData<T> recvdata;
SendData<T> senddata;
};
/**
* @brief The TestData class
* Start the interface with some necessary initial.
*/
class TestData : public SensorT<DesData>
{
public:
TestData() = default;
~TestData(){cout<<"~TestData()"<<endl;}
bool initTestData()
{
if(!Init())
{
cout << "Init recv or send interface Failed!" << endl;
return false;
}
sensorManager_->Addrecv<Son>();
sensorManager_->Addrecv<Daughter>();
sensorManager_->Addsend<Son>();
sensorManager_->Addsend<Daughter>();
RegisterSend<Son>(son);
RegisterSend<Daughter>(daughter);
return true;
}
void UpdateSendData(DesData& data_)
{
daughter->setAge(data_.getDauage());
daughter->setbeautiful(data_.getIsdaubea());
daughter->setMoney(data_.getDaumoney());
son->setAge(data_.getSonage());
son->setMoney(data_.getSonmoney());
senddata.Update();
}
private:
Daughter* daughter = nullptr;
Son* son = nullptr;
};
/**
* @brief The TMain class
* Entrance of the programe.
*/
class TMain
{
public:
TMain() = default;
~TMain(){cout<<"~Tmain()"<<endl;}
void HandleRecvThread(int intervaltimetime)
{
Timepointer tm_start, tm_end;
uint64_t sleep_interval = 0;
cout<<"HandleRecvThread start..."<<endl;
while(1)
{
tm_start = system_clock::now();
DesData dataTemp;
testdata.sensorManager_->GetData(&dataTemp);
_mutex_.lock();
data_ = dataTemp;
_mutex_.unlock();
dataTemp.console();
tm_end = system_clock::now();
sleep_interval = intervaltimetime -
(duration_cast<milliseconds>(tm_end - tm_start).count())/1e6;
if(sleep_interval > 0)
std::this_thread::sleep_for(milliseconds(sleep_interval));
}
}
void UpdateSendThread(int intervaltimetime)
{
cout<<"UpdateSendThread start..."<<endl;
Timepointer tm_start, tm_end;
uint64_t sleep_interval = 0;
DesData senddata;
while(1)
{
tm_start = system_clock::now();
_mutex_.lock();
senddata = data_;
_mutex_.unlock();
testdata.UpdateSendData(senddata);
tm_end = system_clock::now();
sleep_interval = intervaltimetime -
(duration_cast<milliseconds>(tm_end - tm_start).count())/1e6;
if(sleep_interval > 0)
std::this_thread::sleep_for(milliseconds(sleep_interval));
}
}
bool Initial()
{
if(!testdata.initTestData())
ERROR("Init Testdata Failed!");
return true;
}
bool Start()
{
if(!testdata.Start())
ERROR("start recv and send Thread Failed!");
std::thread mainrecvT(&TMain::HandleRecvThread, this, delta_period);
mainrecvT.detach();
//数据的更新周期要小于数据的发送周期
std::thread mainsendT(&TMain::UpdateSendThread, this, delta_period/10);
mainsendT.join();
return true;
}
private:
TestData testdata;
DesData data_;
std::mutex _mutex_;
};
int main()
{
TMain maint;
maint.Initial();
maint.Start();
return 0;
}
util.h 代码:
/******************************************************************************
* Complete by Solitary_Tang(Tang xiao long) at 2021-3-10 11:05.
* Destination: just regard as a tools-lib.
* Language: C++
* System: Linux
* CSND blog link: https://blog.csdn.net/qq_37174816/article/details/114633330
*****************************************************************************/
#ifndef UTIL
#define UTIL
#include <iostream>
#include <memory>
/**
* @brief ERROR complete output errorinfo, filename, function name and row number.
* @param ERR the errorinfo
*/
#define ERROR(ERR)\
{\
std::cout<<"Error: "<<ERR<<", at file:"<<__FILE__<<\
", function: "<<__FUNCTION__<<", line: "<<__LINE__<<std::endl;\
return false;\
}\
/**
* @brief IntchangeBin Get binary with string of T type
* @param x
* @return
*/
template<typename T>
const std::string IntchangeBin(T& x)
{
std::string str = "";
for(int i = sizeof(T)*8-1; i>=0;--i)
str.append(std::to_string((x>>i)&1));
return str;
}
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
#endif // UTIL