auto
关键字:隐式定义,也是强类型定义。在编译期让编译器自动推断出变量类型以便分配内存,必须在定义时进行初始化decltype
关键字:获取表达式的类型typedef
重定义一个模板需要借助外敷类,但是using
别名语法覆盖了typedef
全部功能。使用using
重定义模板会更简洁,定义函数指针会更加清晰。template <typename Val>
struct str_map
{
typedef std::map<std::string, Val> type;
};
// 借助外敷类
str_map<int>::type map1;
=========================
template <typename Val>
using str_map_t = std::map<std::string, Val>;
// 直接重定义一个模板
str_map_t<int> map1;
typedef void(*func_ptr)(int a); //可以把func_ptr理解为一个类
using func_ptr = void(*)(int a); //等价上一条
for_each()
函数可用于很多容器类,它接受3个参数。前面两个是定义容器中区间的迭代器,最后一个是指向函数的指针(是函数对象)for_each()函数将被指向的函数应用于容器区间的各个元素。被指向的函数不能修改容器元素的值(值传递)。可以用for_each()来代替for循环。void f(int n) { //函数对象
...
}
vector<int> v; //容器
for_each(v.begin(), v.end(), f); //只读模式
for
循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。for (auto &ch : myvector){ //使用引用可以修改容器内的值
ch++;
}
for (auto ch : myvector){ //只读模式
cout << ch;
}
可调用对象有如下几种定义:
operator()
成员函数的类对象(仿函数)。std::function
可以取代函数指针的作用
std::bind
用来将可调用对象与其参数一起进行绑定成一个仿函数或者将多元(参数个数为n
,n>1
)可调用对象转成一元或者(n-1
)元可调用对象,只绑定部分参数。
lambda
表达式定义了一个匿名函数,并且可以捕获一定范围内的变量:[ capture ] ( params ) opt -> ret { body; };
auto f = [](int a) -> int { return a + 1; };
std::cout << f(1) << std::endl;
&&
)是指表达式结束时就不再存在的临时对象,右值由两个概念构成,一个是将亡值(xvalue, expiring value
),另一个则是纯右值(prvalue, PureRvalue
),C++11
中所有的值必属于左值、将亡值、纯右值三者之一。#include
using namespace std;
void printValue(int &i)
{
cout << "l-value: " << i << endl;
}
void printValue(int &&i)
{
cout << "r-value: " << i << endl;
}
void forward(int &&k)
{
printValue(k);
}
int main()
{
int i = 520;
printValue(i); // 输出结果l-value: 520
printValue(1314); // 输出结果r-value: 1314
forward(250); // 输出结果l-value: 250
return 0;
};
std::move
可以将一个左值转换成右值,移动语义可以直接使用临时对象已经申请的资源,既能节省资源,又能节省资源申请和释放的时间(避免无谓的深拷贝)。std::move()
将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝,move
对拥有内存、文件句柄等资源的对象有效,如果是POD类型,仍然会发生拷贝。
#include
#include
#include
using namespace std;
int main()
{
string str = "hello";
cout << "before str: " << str << endl;
vector<string> vstr;
vstr.emplace_back(std::move(str)); // str变为空了,原来资源所有权转移到vstr
cout << "after str: " << str << endl;
return 0;
}
注意: 编译器会将已命名的右值引用视为左值,将未命名的右值引用视为右值
在函数内部转发形参时,该参数已经变成了一个左值,并不是原本的类型。完美转发(Perfect Forwarding
)能够按照参数原本的类型转发到另一个函数,保持参数的左值、右值特征。
emplace_back
能就地通过构造函数的参数构造对象,不需要拷贝或者移动内存,对象有对应的构造函数,如果没有对应的构造函数,编译器会报错。相比 push_back
能更好地避免内存的拷贝与移动,使容器插入元素的性能得到进一步提升。
有 序 容 器 map/multimap
和 set/multiset
内部是红黑树,在插入元素时会自动排序,而无序容器内部是散列表(Hash Table
),通过哈希(Hash
),而不是排序来快速操作元素,使得效率更高。对于基本类型来说,不需要提供 Hash 函数和比较函数,用法上和 map/set 一样,对于自定义的结构体,需要提供函数和比较函数。
static
static
后表示该函数失去了全局可见性,只在该函数所在的文件作用域内可见 。static
以后,编译器在该目标编译单元内只含有该函数的入口地址,没有函数名,其它编译单元便不能通过该函数名来调用该函数。mutable
修饰的成员)。const
,加到非成员函数或静态成员后面会产生编译错误。const
的成员函数可以被非const
对象和const
对象调用,加了const
的成员函数可以被非const
对象和const
对象调用#include
using namespace std;
class Test
{
public:
Test(int a) {this->a = a;}
static void print_static() //const //1.错误
{
cout<< "静态成员函数" <<endl;
}
void print_const() const
{
//a++; //2.错误
cout << "加const的成员函数" <<endl;
}
void print()
{
cout<< "普通成员函数" <<endl;
}
private:
int a;
};
int main()
{
const Test t1(10);
Test t2(20);
t1.print_const();
//t1.print(); //3.错误 const对象不能调用非const函数
t2.print_const();
t2.print();
return 0;
}
this
是 C++ 中的一个关键字,也是一个 const
指针,它指向当前对象,通过它可以访问当前对象的所有成员。
class Student{
public:
void setname(char *name);
private:
char *name;
};
// 成员函数的参数和成员变量重名,只能通过 this 区分。成员函数setname(char *name)的形参是name,和成员变量name重名,如果写作name = name;这样的语句,就是给形参name赋值,而不是给成员变量name赋值。
void Student::setname(char *name){
this->name = name;
}
两个类不能互相include
对方的头文件,因为两个类相互引用,不管哪个类在前面,都会出现有一个类未定义的情况,所以可以提前声明一个类,而类的声明就是提前告诉编译器,所要引用的是个类,但此时后面的那个类还没有定义,因此无法给对象分配确定的内存空间,因此只能使用类指针。
//A.h
#include "B.h"
class A
{
B b;
};
//B.h
#include "A.h"
class B
{
A a;
};
通过声明一个不完全的类,且只能使用类指针,不能定义对象实体。
//B.h
class A; //声明一个不完全的类
class B;
{
//如果直接定义A类的对象,则编译器会报错的,因为是一个不完整定义的类
//A a; 错误
//必须 智能声明一个之前A的指针,这样只是一个指针,而不需要知道A 是否定义。
A * a;
/*还有如果定义成员函数的话,用于返回类型、参数类型也是可以,但不能直接在 B类里直接定义函数,
需要在定义完A类的定义 之后才能跟在后面 外部定义函数。*/
A func(A); //这样也是可行的。
//所以 直接声明什么的无所谓,只要不是直接定义一个实体对象出来就可以。
}
#define BYTE0(data) (*(char*)(&data))
#define BYTE1(data) (*((char*)(&data) + 1))
#define BYTE2(data) (*((char*)(&data) + 2))
#define BYTE3(data) (*((char*)(&data) + 3))
#define BYTE4(data) (*((char*)(&data) + 4))
#define BYTE5(data) (*((char*)(&data) + 5))
#define BYTE6(data) (*((char*)(&data) + 6))
#define BYTE7(data) (*((char*)(&data) + 7))
uint8_t resolutionData[4];
float resolution = *(float*)resolutionData;
std::shared_ptr
、std::uniqe_ptr
、std::weak_ptr
是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保在离开指针所在作用域时,通过使用引用计数自动正确地销毁动态分配的对象,防止内存泄漏,使用时需要引用头文件
。
std::shared_ptr
使用引用计数,每一个shared_ptr
的拷贝都指向相同的内存,在最后一个析构时内存才会被释放。std::shared_ptr<int> p(new int(1)); //智能指针的初始化
std::shared_ptr<int> p2 = p; //智能指针的赋值
std::shared_ptr<int> ptr; //对于未初始化的智能指针,可以通过reset初始化
ptr.reset(new int(1));
std::shared_ptr<int> p = new int(1); //编译报错,不允许直接赋值
malloc
函数只能返回第一个字节的地址,这个是系统规定的,第一个字节的地址没有实际含义。因为不能根据第一个字节地址来确定这个变量占了几个字节。因为整形变量占四个字节,它也是以第一个字节地址来表示的,如果这个变量是double
类型的话,它也是以第一个字节地址来表示的。无论这个变量占几个字节它都是以第一个字节来表示的,所以malloc
返回的第一个字节地址是没有实际意义的地址(干地址)。所以我们需要在 malloc(sizeof(int)*len)
的前面加一个强制类型转换 (int*)malloc(sizeof(int)*len)
来告诉我们的编译器,我们返回的第一个字节地址到底是整型的地址,还是其他类型的地址。所以强制转换成 int *
的话,那么我们的 pArr+1
的话那么它将会指向后面的一个 int *
的位数,会指向后四位,如果强制类型是 double *
的话, pArr + 1
将会指向后八位的地址。
int * pArr = (int *)malloc(sizeof(int) * len); //这里要使用强制类型转化
//do something
free(pArr); //把pArr所代表的动态分配的20个字节内存释放
用std::thread
创建线程需要提供线程函数和函数对象,同时可以指定线程函数的参数,线程不可以复制,但可以移动std::move()
。
#include
void func( int id, int numIter )
{
//函数执行体
}
void main1() {
std::thread t1( func, 1, 6 );
t1.join(); //**如果不希望线程被阻塞执行,使用detach()可以将线程和线程对象分离**
}
join()
会阻塞线程,直到线程函数执行结束detach()
将线程和线程对象分离,但是线程对象无法再和线程发生联系,无法控制线程结束thread_name.get_id()
std::this_thread::sleep_for(std:;chrono::seconds(3))
互斥量(mutex
)是一种同步原语,是一种线程同步的手段,用来保护多线程同时访问的共享数据。
std::mutex
,独占的互斥量,不能递归使用。一个线程多次获取同一个互斥量会发生死锁std::time_mutex
,带超时的独占互斥量,不能递归使用。std::recursive_mutex
,递归互斥量,不带超时功能。允许同一个线程多次获得该互斥锁std::recursive_timed_mutex
,带超时的递归互斥量lock()
方法阻塞线程,直到获得互斥量的所有权,在完成任务后通过unlock()
解除对互斥量的占用。tyr_lock()
尝试锁定互斥量,是非阻塞的。使用lock_guard
可以简化写法,同时更安全(在构造时会自动锁定互斥量,在退出作用域时会自动解锁,保证了互斥量的正确操作)#include
#include
#include
#include
using namespace std;
mutex mt;
void thread_task()
{
for (int i = 0; i < 10; i++)
{
lock_guard<mutex> guard(mt);
cout << "print thread: " << i << endl;
}
}
int main()
{
thread t(thread_task);
for (int i = 0; i > -10; i--)
{
lock_guard<mutex> guard(mt);
cout << "print main: " << i << endl;
}
t.join();
return 0;
}
条件变量是利用线程间共享的全局变量进行同步(按照预定的先后次序顺序进行)的一种机制,主要包括两个动作:线程等待条件变量的条件成立而挂起;线程变量的条件成立给出条件成立信号。
工作原理:
duration
:时间间隔,原型模板中第一个参数Rep
是数值类型(int
、double
…),第二个模板参数std::ratio
表示时钟周期:表示Num/Denom
秒。template<
class Rep,
class Period = std;:ratio<1,1>
> class duration;
template<
std::intmax_t Num,
std::intmax_t Denom = 1
> class ratio;
线程的休眠可以使用时间间隔(例如std::chrono::duration<3.5, std::ratio<1,30>>
),常用的时间间隔有:
typedef duration <ReP,ratio<3600,1>> hours;
typedef duration <ReP,ratio<60,1>> minutes;
typedef duration <Rep, ratio<1,1>> seconds;
typedef duration <Rep, ratio<1,1000>> milliseconds;
typedef duration <Rep,ratio<1,1000000>> microseconds;
typedef duration <Rep,ratio<1,1000000000>> nanoseconds;
不同时钟周期的时间间隔可以做运算,通过std::chrono::duration_cast
可以转换成分钟的时间间隔。
clocks
:系统时钟,system_clock、steady_clock、high_resolution_clock
std::chrono::system_clock
它表示当前的系统时钟,系统中运行的所有进程使用now()
得到的时间是一致的。std::chrono::steady_clock
为了表示稳定的时间间隔,后一次调用now()
得到的时间总是比前一次的值大,即使中途修改了系统时间,也不影响的结果,每次tick
都保证过了稳定的时间间隔。system_clock::time_point today = system_clock::now();
system_clock::time_point tomorrow = today + hours(24);
steady_clock::time_point start = steady_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(2));
steady_clock::time_point end = steady_clock::now();
auto elapsed = end - start;
cout << endl << elapsed.count() << endl;
cout << duration<double>(elapsed).count() << endl; // converts to seconds
time point
:时间点,获取从开始纪元(可能是1970.1.1)所经历过的duration
和当前时间// 原型模板
template <class Clock, class Duration = typename Clock::duration> class time_point;
std::put_time(std::local_time())
可以格式化日期的输出,且利用high_resolution_clock
可以实现计时器功能,方便用于测试函数耗时。
string
存储单字节编码的字符(ASCII编码格式),wstring
存储双字节编码的字符(UNICODE编码格式,比如中文、日文等)
std::wstring str = L"中国人"; //定义unicode 字符串,用L定义宽字符
codecvt
库中的wstring_convert
to_string(value)
和to_wstring(value)
atoi
:将字符串转为int
类型atol
:将字符串转为long
类型atoll
:将字符串转为long long
类型atof
:将字符串转为float
类型标准库中提供数值型别的类型:numeric_limits<>
Templates
以核定(evaluate
)这些极值#include
cout << "max(short): " << numeric_limits<short>::max() << endl;
cout << "max(int): " << numeric_limits<int>::max() << endl;
cout << "max(long): " << numeric_limits<long>::max() << endl;
denorm_min | 返回最小的非规范化非零值。 |
digits | 返回类型可以表示而不会降低精度的基数数字的位数。 |
digits10 | 返回类型可以表示而不会降低精度的十进制数字的位数。 |
epsilon | 返回数据类型可以表示的 1 与大于 1 的最小值之间的差值。 |
has_denorm | 测试类型是否允许非规范化值。 |
has_denorm_loss | 测试是否将准确度降低检测为非规范化损失,而不是不准确结果。 |
has_infinity | 测试某一类型是否能够表示正无穷。 |
has_quiet_NaN | 测试某一类型是否能表示非信号性沉寂非数值 (NAN)。 |
has_signaling_NaN | 测试某一类型是否能表示信号性沉寂非数值 (NAN)。 |
infinity | 某一类型用于表示正无穷的值(若适用)。 |
is_bounded | 测试某一类型可表示的值设置是否为有限。 |
is_exact | 测试针对某一类型进行的计算是否不产生舍入错误。 |
is_iec559 | 测试某一类型是否符合 IEC 559 标准。 |
is_integer | 测试某一类型是否具有具有整数表示形式。 |
is_modulo | 测试某一类型是否具有具有取模表示形式。 |
is_signed | 测试某一类型是否具有带符号的表示形式。 |
is_specialized | 测试某一类型是否具有在类模板 numeric_limits 中定义的显式专用化。 |
lowest | 返回最小的负有限值。 |
max | 返回某个类型的最大有限值。 |
max_digits10 | 返回确保类型的两个非重复值具有不同的十进制表示形式所需的十进制数字的位数。 |
max_exponent | 返回最大正整数指数,当计算基数的该指数次幂时,浮点类型可将其表示为有限值。 |
max_exponent10 | 返回最大正整数指数,当计算 10 的该指数次幂时,浮点类型可将其表示为有限值。 |
min | 返回某个类型的最小规范化值。 |
min_exponent | 返回最大负整数指数,当计算基数的该指数次幂时,浮点类型可将其表示为有限值。 |
min_exponent10 | 返回最大负整数指数,当计算 10 的该指数次幂时,浮点类型可将其表示为有限值。 |
quiet_NaN | 返回类型的静默非数值 (NAN) 表示形式。 |
radix | 返回用于表示类型的整数底数(称为基数)。 |
round_error | 返回类型的最大舍入误差值。 |
round_style | 返回一个值,该值描述可供实现选择用于将浮点值舍入为整数值的各种方法。 |
signaling_NaN | 返回类型的信令非数值 (NAN) 表示形式。 |
tinyness_before | 测试某个类型是否可在舍入某个值之前确定该值太小而无法表示为规范化值。 |
traps | 测试是否为某个类型实现了报告算术异常的捕获。 |
std::make_unique
std::make_unique
创建并获取unique_ptr
类型的智能指针,相比unique_ptr
更加安全。可以取代new
而且无需清空指针,尽量使用std::make_unique
和std::make_shared
,而不是new。template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params){
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}
make
函数( std::make_unique
、std::make_shared
和std::allocate_shared
(允许传递你自己的分配器作为第一个参数))用来把一个任意参数的集合完美转移给一个构造函数从而生成动态分配内存的对象,并返回一个指向那个对象的smart pointer
。share_ptr
限定的资源可以被多个指针共享,weak_ptr
是一种用于解决shared_ptr
相互引用时产生死锁问题的智能指针,unique_ptr
是一个独享所有权的智能指针(必须是指向动态分配的对象的指针)。
auto upw1(std::make_unique<int>()); // with make func
std::unique_ptr<int> upw2(new int); // without make func
auto spw1(std::make_shared<int>()); // with make func
std::shared_ptr<int> spw2(new int); // without make func
区别:使用new
的版本重复了被创建对象的键入,执行两次内存分配:一次内存分配给X,一次内存分配给控制块,但是make
函数则没有。
int i = 0b0100010001; // 273
int i = 0b01'0001'0001; // 273
读写锁(shared_lock
)可以有三种状态,只有一个线程可以占有写模式的读写锁,但是可以有多个线程占有读模式的读写锁。
当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。相比互斥锁,读写锁允许更高的并行性,互斥量要么锁住状态要么不加锁,而且一次只有一个线程可以加锁。
socket
: 建立一个socket
,原型为int socket(int af, int type, int protocol)
,返回本次socket
连接的文件句柄。
af
为地址族(Address Family
),也就是 IP 地址类型,常用的有 AF_INET
和 AF_INET6
。AF_INET
表示 IPv4
地址,例如 127.0.0.1,表示本机地址;AF_INET6 表示 IPv6 地址。type
为数据传输方式/套接字类型,常用的有 SOCK_STREAM
(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM
(数据报套接字/无连接的套接字)。protocol
表示传输协议,常用的有 IPPROTO_TCP
和 IPPTOTO_UDP
,分别表示 TCP 传输协议和 UDP 传输协议。如果将protocol
的值设为 0,系统会自动推演出应该使用什么协议.sockaddr_in
: 处理网络通信的地址的结构体struct sockaddr_in {
short sin_family; // 2 字节 ,地址族,e.g. AF_INET, AF_INET6
unsigned short sin_port; // 2 字节 ,16位TCP/UDP 端口号 e.g. htons(3490),
struct in_addr sin_addr; // 4 字节 ,32位IP地址
char sin_zero[8]; // 8 字节 ,不使用
};
// 创建 sockaddr_in 实例
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口号
bind
: 将这个socket
绑定在某个端口上(AF_INET
)recvfrom
: 如果没有客户端发起请求,则会阻塞在这个函数里//接收函数
int recvfrom(int sockfd, void * buf, size_t len, int flags, struct sockaddr * src_addr, socklen_t * addrlen);
//参数含义
sockfd:用于接收UDP数据的套接字;
buf:保存接收数据的缓冲区地址;
len:可接收的最大字节数(不能超过buf缓冲区的大小);
flags:可选项参数,若没有可传递0;
src_addr:存有发送端地址信息的sockaddr结构体变量的地址;
addrlen:保存参数 src_addr的结构体变量长度的变量地址值。
close
: 通信完成后关闭socket
socket
: 建立一个socket
sockaddr_in
: 处理网络通信的地址的结构体sendto
:向服务器的某个端口发起请求(AF_INET
)//发送函数
int sendto(int sockfd, const void * buf, size_t len, int flags, const struct sockaddr * dest_addr, socklen_t addrlen);
//参数含义
sockfd:用于传输UDP数据的套接字;
buf:保存待传输数据的缓冲区地址;
len:带传输数据的长度(以字节计);
flags:可选项参数,若没有可传递0;
dest_addr:存有目标地址信息的 sockaddr 结构体变量的地址;
addrlen:传递给参数 dest_addr的地址值结构体变量的长度。
close
: 通信完成后关闭socket
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEST_PORT 8000
#define DSET_IP_ADDRESS "127.0.0.1"
int main()
{
/* socket文件描述符 */
int sock_fd;
/* 建立udp socket */
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 设置address */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(addr_serv));
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = inet_addr(DSET_IP_ADDRESS);
addr_serv.sin_port = htons(DEST_PORT);
len = sizeof(addr_serv);
int send_num;
int recv_num;
char send_buf[20] = "hey, who are you?";
char recv_buf[20];
printf("client send: %s\n", send_buf);
send_num = sendto(sock_fd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&addr_serv, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_serv, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("client receive %d bytes: %s\n", recv_num, recv_buf);
close(sock_fd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 8000
int main()
{
/* sock_fd --- socket文件描述符 创建udp套接字*/
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 将套接字和IP、端口绑定 */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(struct sockaddr_in)); //每个字节都用0填充
addr_serv.sin_family = AF_INET; //使用IPV4地址
addr_serv.sin_port = htons(SERV_PORT); //端口
/* INADDR_ANY表示不管是哪个网卡接收到数据,只要目的端口是SERV_PORT,就会被该应用程序接收到 */
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP地址
len = sizeof(addr_serv);
/* 绑定socket */
if(bind(sock_fd, (struct sockaddr *)&addr_serv, sizeof(addr_serv)) < 0)
{
perror("bind error:");
exit(1);
}
int recv_num;
int send_num;
char send_buf[20] = "i am server!";
char recv_buf[20];
struct sockaddr_in addr_client;
while(1)
{
printf("server wait:\n");
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_client, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("server receive %d bytes: %s\n", recv_num, recv_buf);
send_num = sendto(sock_fd, send_buf, recv_num, 0, (struct sockaddr *)&addr_client, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
}
close(sock_fd);
return 0;
}
asio
的主动器发起异步连接:// 定义asio的核心对象io_service,所有的io object对象的初始化都要将这个对象传入
asio::io_service io_service;
// 创建tcp::socket类型的io object对象,通过这个io object对象发起异步操作
asio::ip::tcp::socket socket(io_service);
// 发起异步连接操作
boost::asio::async_connect(socket,server_address, connect_handler);
// 调用io_service的run启动事件循环,等待异步事件的完成
io_service.run();
#include
using boost;
using boost::asio;
using boost::asio::ip;
class RWHandler {
public:
RWHandler(boost::asio::io_service& io_service) : socket_(io_service) {}
tcp::socket& socket() { return socket_; }
void HandleRead() {
async_read(socket_, buffer(m_buff), [this](const boost::system::error_code& ec, size_t size));
if (!ec) {
HandleError(ec);
return;
}
HandleRead();
}
void Handlewrite(char* data, int len) {
boost::system::error_code ec;
write(socket_, buffer(data, len), ec);
if (!error) {
HandleError(ec);
}
}
private:
tcp::socket socket_; // 具体的读写执行者
enum { max_length = 1024 }; // 缓冲区长度
std::array<char max_length> buffer; // 缓冲数据
};
在处理大量并发任务时,传统大量的线程创建和销毁会消耗过多的系统资源,而且增加了线程上下文切换的开销。线程池技术通过预先创建一定数量的线程,从线程池中分配一个预先创建的线程去处理任务,这种方式好处:
// 同步队列的实现 保证线程安全
template <typename T>
class SyncQueue {
public:
SyncQueue(int max_size): max_size_{max_size} {}
void Push(T&& task) {
std::unique_lock<std::mutex> lock{mutex_};
not_full_variable_.wait(lock, [this]{ return queue_.size() < max_size_; });
queue_.push(std::forward<T>(task));
not_empty_variable_.notify_one();
}
void Pop(T& task) {
std::unique_lock<std::mutex> lock{mutex_};
not_empty_variable_.wait(lock, [this]{ return !queue_.empty(); });
task = queue_.front();
queue_.pop();
not_full_variable_.notify_one();
}
void Pop(std::queue<T>& tasks) {
std::unique_lock<std::mutex> lock{mutex_};
not_empty_variable_.wait(lock, [this]{ return !queue_.empty(); });
tasks = std::move(queue_);
not_full_variable_.notify_one();
}
bool Empty() {
std::lock_guard<std::mutex> lock{mutex_};
return queue_.empty();
}
bool Full() {
std::lock_guard<std::mutex> lock{mutex_};
return max_size_ == queue_.size();
}
private:
std::mutex mutex_;
std::condition_variable not_full_variable_;
std::condition_variable not_empty_variable_;
std::queue<T> queue_;
int max_size_;
};
class ThreadPool {
// ...
private:
void threadFunction(); // 线程执行函数的声明
vector<thread> threads; // 线程集合
// 任务队列相关
deque<Task> taskQueue; // 任务队列
mutex queueMutex; // 任务队列访问互斥量
condition_variable condition; // 任务队列条件变量,通知线程有新任务可执行, 实现任务队列的同步访问
};
// 创建线程集合
ThreadPool::ThreadPool(size_t threadCount) {
for (size_t i = 0; i < threadCount; ++i) {
threads.emplace_back(&ThreadPool::threadFunction, this);
}
}
// 添加任务,需使用互斥量锁住任务队列以实现同步访问。任务添加成功后,通知等待中的线程有新任务可以执行。
void ThreadPool::addTask(const Task& task) {
{
lock_guard<mutex> lock(queueMutex);
taskQueue.emplace(task);
}
condition.notify_one();
}
// 线程执行体应按照预设策略从任务队列中获取任务并执行。获取任务时,需要在条件变量上等待,直到有新任务或线程池被终止。任务获取成功后,线程从队列中移除任务并执行。执行完成后,线程可以被再次复用。
void ThreadPool::threadFunction() {
while (true) {
Task task;
{
unique_lock<mutex> lock(queueMutex);
condition.wait(lock, [this]() { return !taskQueue.empty() || terminate; });
if (terminate && taskQueue.empty()) {
break;
}
task = taskQueue.front();
taskQueue.pop();
}
task(); // Execute the task.
}
}