C++11新增
auto关键字:编译器可以根据初始值自动推导出类型。但是不能用于函数传参以及数组类型的推导;
nullptr关键字:nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;而NULL一般被宏定义为0,在遇到重载时可能会出现问题;
智能指针:C++11新增了std::shared_ptr、std::weak_ptr等类型的智能指针,用于解决内存管理的问题。
初始化列表:使用初始化列表来对类进行初始化;
右值引用:基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率;
新增STL容器array以及tuple;
std::move(),std::forward()。
可变参数模板
可变参数模板是可以接受任意参数个数(n (ge) 0)的模板,定义为:
template<typename ...T>
对于类来说,可变参数模板只能是最后一个参数,不然无法推导出对应的类型。
template<typename ... T, typename U>
class A{}; //错
template<typename U, typename .. T>
class A{}; //对
对于函数,template中的顺序可以随便写,但是函数声明/定义中可变参数模板之恩你是最后一个参数。
template<typename ... T, typename U>
void f(U u, T ... t){} //对
template<typename U, typename .. T>
void f(T ... t, U u){} //错
可以使用递归方式展开参数包
template<typename T, typename ... Args>
void print(T head, Args ... arg){
cout << head<<endl;
print(arg...);
}
移动语义
references:
[c++11]我理解的右值引用、移动语义和完美转发
移动语义是为了防止资源浪费所出现的。在编码中,我们可能会遇到一种情况:我们会得到一个临时变量然后传参,那么会调用一次拷贝构造。但是显然,拷贝构造将临时变量的内容复制了一遍,然后临时变量被释放掉了。这样显然是很浪费的。所以C++支持了移动语义。
实现移动语义:移动构造和移动赋值。
在移动构造时,我们用右值引用把临时变量传进来,与拷贝构造不同,它并不是重新分配一块新的空间,不是将要拷贝的对象复制过来,而是"偷"了过来,将自己的指针指向别人(右值)的资源,然后将别人的指针修改为nullptr,这一步很重要,如果不将别人的指针修改为空,那么临时对象析构的时候就会释放掉这个资源。这样就减少了复制的消耗。
C++提供了std::move()函数将左值变成右值,这样vector,set就可以使用移动构造了,提高了效率。但是move(a)之后,a的所有指针均为空,故不要再使用。
下面为自定义移动构造和移动赋值。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int maxn = 2e5 + 5;
class String{
public:
String(char *str = 0){ //构造函数
if(str){
int len = strlen(str);
s = new char[len + 1];
strncpy(s, str, len);
}
else{
str = new char[1];
str[0] = 0;
}
}
String(String &&a) : s(a.s){ //移动构造
cout << "move" << endl;
a.s = nullptr;
}
String& operator = (String &&a){ //移动赋值
cout << "move=" << endl;
if(this == &a){
return *this;
}
delete[] s;
s = a.s;
a.s = nullptr;
return *this;
}
void print();
private:
char* s;
};
void String::print(){
cout << s << endl;
}
int main() {
vector<String> a;
a.push_back(String("test")); //move
return 0;
}
完美转发和std::forward()
references:
c++11 中的 move 与 forward
就是之前右值引用中讲的,模板函数形参使用右值引用,那么传进来是什么类型的左值/右值,就是什么类型的左值/右值。
假如我们有一个重载的函数f(int&)
和f(int&&)
,当我们传了一个值给template<typename T>G(T&& a)
,让模板类G调用f,但是G虽然接受了左值/右值a,但是G是不知道他的类型的,但是a有名字,那么a就会被认为是一个左值,自动调用f(int&)
。所以std::forward()
的作用是接受一个参数,然后返回该参数本来所对应的类型的引用。
void f(int &x){
cout << "left" << endl;
}
void f(int &&x){
cout << "right" << endl;
}
template<typename T>
void G(T&& a){
f(std::forward<T>(a)); //left right
f(a); //left left
}
int main() {
int x = 1;
G(x);
G(1);
return 0;
}
I/O复用之select、poll、epoll
references:
细谈Select,Poll,Epoll
聊聊IO多路复用之select、poll、epoll详解
epoll底层细节
select:
基本原理:
调用后,select函数阻塞,kernel(内核)就会轮询所有的fd(文件描述符),直到有描述符就绪(可读、可写、except)或者超时(timeout设定时间,立即返回设null),函数返回。当select函数返回后,可以通过遍历fd_set(long数组)找到就绪的描述符。
优点:
良好的跨平台支持,几乎所有平台都支持。
缺点:
1.单个进程打开的fd数量有限制,大小为FD_SETSIZE,默认32位为1024个,64位为2048个;
2.采用轮询方式,效率低,约线性时间;
3.需要维护一个存放fd的fd_set,在用户空间和内核空间复制消耗资源大;
特点:
select还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次select时会再次报告该fd。
poll
基本原理:
和select基本相同,只是描述fd集合的方式从fd_set变成了pollfd(链表)。
优点:
因为采用链表,所以没有最大连接数的限制。
缺点:
1.采用轮询方式,效率低,约线性时间;
2.pollfd复制于用户空间和内核空间之间,不管这样的复制是否有意义,传递消耗资源大;
特点:
poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
epoll(事件驱动)
基本原理:
epoll在内核中申请一个文件系统,用来存储需要监听的socket,并且用红黑树的方式存储,用链表存储就绪的事件。通过epoll_ctl注册fd,一旦该fd就绪(就绪链表中有节点),就会用一个回调函数来激活该fd,epoll_wait便可以收到通知。
-
int epoll_create(int size);
建立一個 epoll 对象,并传回它的id -
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
事件注册函数,将需要监听的事件和需要监听的fd交给epoll对象 -
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待注册的事件被触发或者timeout发生
水平触发(LT)和边缘触发(ET):
LT(默认):
当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET:
当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
优点:
1.几乎没有数量限制,数量上限只和能打开的fd有关;
2.不是轮询,而是被动触发,效率提升;
3.不需要大量在用户空间和内核空间之间拷贝,epoll在用epoll_ctl函数进行事件注册的时候,已经将fd复制到内核中,所以不需要每次都重新复制一次。共享一块内存空间。
缺点:
需要很多函数回调,fd很少时性能可能比select/poll差。
为什么构造函数不能是虚函数
references:为什么构造函数不能声明为虚函数?
1、构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。
2、虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。