在工作和开源优秀的C++框架,很普遍的使用C++11的优秀特性,在公司内部也做过C++11/14/17的分享,将分享的内容整理于此,C++20的module/协程等特性大概得在2023才有官方的框架,因此没有做分享,仅自身了解了一下~
文章目录
一、关键字
1.nullptr
在一些编译器中,会把NULL定义为0,使用nullptr区分空指针和0char* p = nullptr;
2.auto
对变量进行类型推导声明,不能够对数组推导,不能作为函数参数vector<int> vec;
vector<int>::iterator iter = vec.begin() //c++11 before
auto iter = vec.begin(); //c++11
3.decltype
从一个变量或表达式中得到类型template<typename T, typename U>
auto add2(T x, U y) -> decltype(x+y){
return x + y;
}
二、语法优化
1.区间迭代for循环
方便的for循环写法std::vector<int> vec;
//before C++11
for(vector<int>::iterator it = vec.begin(); it != vec.end(); ++it)
//end C++11
for(auto it : vec);
2.类型别名
定义类型别名(具有typedef的作用)//before C++11
typedef unsigned int uint_t;
//end C++11
using uint_t = unsigned int;
//before
typedef int (*process)(void *);
//end
using NewProcess = int(*)(void *);
3.显式禁用默认函数
class T
{
T(){
p = new int(1);
}
~T(){
delete p;
}
public:
int* p;
};
int main(){
T a;
T b = a;
return 0;
}
在main函数第二行,程序会先调用默认构造函数,再调用默认赋值函数,最后的结果默认就是浅拷贝,因此在释放一个变量的空间时,另一个变量会无法访问,指向未知空间,形成悬垂指针,会引发程序coredump
class Magic {
public:
Magic() = default; // 显式声明使用编译器生成的构造
Magic(const Magic& ) = delete;
Magic& operator=(const Magic&) = delete; // 显式声明拒绝编译器生成构造
Magic(int magic_number);
}
防止出现不可控的情形,显示拒绝部分函数自动生成默认函数
4.tuple
一个新的结构,是对pair的一种泛化,std::paid只支持两个元素的存储,在过去写一个函数接口,返回值有多个的时候,都是要封装在一个结构体中,返回这个结构体类型std::tuple<int, double, std::string> f() {
return std::make_tuple(1, 2.3, "456");
}
5.lambda表达式
类似于匿名函数,适合需要使用一个函数,又不想去声明定义这个函数 语法形式: [函数对象参数] (参数列表) mutable 或 exception 声明 -> 返回值类型 {函数体}//before C++11
bool cmp(int a, int b){
return a < b;
}
sort(vec.begin(), vec.end(), cmp);
//end C++11
sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; });
6.bind和function
通过std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象,可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。 相当于函数的容器,存放函数// 普通函数
int add(int a, int b){return a+b;}
// lambda表达式
auto mod = [](int a, int b){ return a % b;}
// 函数对象类
struct divide{
int operator()(int denominator, int divisor){
return denominator/divisor;
}
};
std::function就可以将上述类型保存起来
std::function<int(int ,int)> a = add;
std::function<int(int ,int)> b = mod;
std::function<int(int ,int)> c = divide();
bind是一种机制,可以预先把指定的可调用的实体的某些参数绑定到已有的变量,产生一个新的可调用实体。 std::bind是一个函数模板,就像是一个函数适配器,接受一个可调用对象,而生成一个新的可调用对象来适配原来的参数列表,该模板返回的是一个std::function对象。用该函数我们也能实现参数顺序的调整和给将指定参数设置成固定值。
bind是需要搭配function来使用,function存储函数,bind是绑定参数,在工作中,以前都是定义结构体,结构体中函数指针存放回调函数,其余变量存放函数的参数,现在可以使用bind来解决。
class A
{
public:
int i_ = 0;
void output(int x, int y)
{
std::cout << x << "" << y << std::endl;
}
};
int main()
{
A a;
// 绑定成员函数,保存为仿函数
std::function<void(int, int)> fr = std::bind(&A::output, &a, std::placeholders::_1, std::placeholders::_2);
// 在合适的地方调用成员函数并传入参数
fr(1, 2);
}
std::placeholders是用来占位的,表示在调用的时候才会传参数
需要注意的一点,bind函数默认是传值,如果需要传饮用,需要使用ref(arg)的形式
7.右值引用
左值:顾名思义就是赋值符号左边的值。准确来说, 左值是表达式(不一定是赋值表达式)后依然存在的持久对象。 右值:指表达式结束后就不再存在的临时对象 通俗理解就是左值持久,右值临时Int a = 1; // a是左值 1是右值
Int& b = a; //左值引用
Int& c = 1; //错误
Int&& d = 1; //右值引用
Int func(int a, int b){
return a + b;
}
Int a= func(1,2); // a左值, func(1,2)右值
右值引用的两大用途:移动语义和完美转发
传统的 C++ 没有区分『移动』和『拷贝』的概念,造成了大量的数据拷贝,浪费时间和空间。 右值引用的出现恰好就解决了这两个概念的混淆问题
class A {
public:
int *pointer;
A():pointer(new int(1)) {
std::cout << "构造" << pointer << std::endl;
}
A(A& a):pointer(new int(*a.pointer)) {
std::cout << "拷贝" << pointer << std::endl;
} // 无意义的对象拷贝
A(A&& a):pointer(a.pointer) {
a.pointer = nullptr;
std::cout << "移动" << pointer << std::endl;
}
~A(){
std::cout << "析构" << pointer << std::endl;
delete pointer;
}
};
int main() {
std::string str = "Hello world.";
std::vector<std::string> v;
// 将使用 push_back(const T&), 即产生拷贝行为
v.push_back(str);
// 将输出 "str: Hello world."
std::cout << "str: " << str << std::endl;
// 将使用 push_back(const T&&), 不会出现拷贝行为
// 而整个字符串会被移动到 vector 中,所以有时候 std::move 会用来减少拷贝出现的开销
// 这步操作后, str 中的值会变为空
v.push_back(std::move(str));
// 将输出 "str: "
std::cout << "str: " << str << std::endl;
return 0;
}
对于右值引用来说,仅仅是移动,原临时对象失效,并不会拷贝,提升了性能
完美转发就是为了让我们在传递参数的时候, 保持原来的参数类型(左引用保持左引用,右引用保持右引用)。
template<typename T>
void print(T & t){
std::cout << "左值" << std::endl;
}
template<typename T>
void print(T && t){
std::cout << "右值" << std::endl;
}
template<typename T>
void testForward(T && v){
print(v);
print(std::forward<T>(v));
print(std::move(v));
}
int main(int argc, char * argv[])
{
testForward(1);
std::cout << "======================" << std::endl;
int x = 1;
testFoward(x);
}
结果:
左值
右值
右值
==========
左值
左值
右值
上述例子,可以看出来,在函数传参中,一个本该是左值的,但是最后确打印了右值, 为了解决这个问题,我们应该使用 std::forward 来进行参数的转发(传递)