总结归纳:C++ 17 新特性
关键字
constexpr
扩展
#include<iostream>
template<bool ok>
constexpr void foo()
{
//在编译期进行判断,if和else语句不生成代码
if constexpr (ok == true)
{
//当ok为true时,下面的else块不生成汇编代码
std::cout << "ok" << std::endl;
}
else
{
//当ok为false时,上面的if块不生成汇编代码
std::cout << "not ok" << std::endl;
}
}
int main()
{
foo<true>();//输出ok,并且汇编代码中只有std::cout << "ok" << std::endl;这一句
foo<false>();//输出not ok,并且汇编代码中只有std::cout << "not ok" << std::endl;这一句
return 0;
}
static_assert
扩展
如:
static_assert(true, "");
static_assert(true);//c++17支持
typename
扩展用法,允许出现在模板的模板的参数中。
首先回顾一下
如
struct A
{
typedef int Example;
};
//第一种用法:声明模板参数为类型
template<typename T>
struct B { };
struct C
{
typedef typename A::Example E;//第二种用法:声明某名字为一种类型
};
int main()
{
typename A::Example e;//第二种用法:声明某名字为一种类型
return 0;
}
新特性下的
如:
#include<iostream>
#include<typeinfo>
template<typename T>
struct A
{
int num;
A()
{
std::cout << "A Construct" << std::endl;
std::cout << "template typename is: " << typeid (T).name() << std::endl;
}
};
//此处的T可省略,X代表模板类型,T和X前的typename可替换成class
template<template<typename T> typename X>
struct B
{
X<double> e;
B() { std::cout << "B Construct" << std::endl; }
};
int main()
{
A<B<A>> a;
std::cout << "***************************" << std::endl;
B<A> b;
return 0;
}
inline
扩展用法,可用于定义内联变量,功能与内联函数相似。
如(不发生错误
// test.h
inline void print()
{
std::cout << "hello world" << std::endl;
}
inline int num = 0;
// func.h
include "test.h"
inline void add(int arg)
{
num += arg;
print();
}
// main.cpp
include "func.h"
int main()
{
num = 0;
print();
add(10);
return 0;
}
auto
从
使用
// c++11
auto x3{ 1, 2 }; // std::initializer_list<int>
auto x4 = { 3 }; // decltype(x4) is std::initializer_list<int>
auto x5{ 3 }; // std::initializer_list<int>
这三种方式初始化的变量,最终类型推导的结果都是
当用于
在
// c++17
auto x3{ 1, 2 }; // error: not a single element
auto x4 = { 3 }; // decltype(x4) is std::initializer_list<int>
auto x5{ 3 }; // decltype(x5) is int
对比发现,
语法
lambda 表达式
在
条件表达式中支持初始化语句
// c++17之前
map<int, string> c = { {1,"a"}};
{
auto res = c.insert(make_pair(2, "b"));
if(!res.second) {
cout << "key 1 exist" << endl;
} else {
cout << "insert success, value:" << res.first->second << endl;
}
}
上面的一段代码,由于
在
// c++17
map<int, string> c = { {1,"a"}};
if(auto res = c.insert(make_pair(2, "b")); !res.second ) {
cout << "key 1 exist" << endl;
} else {
cout << "insert success, value:" << res.first->second << endl;
}
折叠表达式
用于变长参数模板的解包,只支持各种运算符(和操作符
如:
#include<string>
template<typename ... T>
auto sum(T ... arg)
{
return (arg + ...);//右折叠
}
template<typename ... T>
double sum_strong(T ... arg)
{
return (arg + ... + 0);//右折叠
}
template<typename ... T>
double sub1(T ... arg)
{
return (arg - ...);//右折叠
}
template<typename ... T>
double sub2(T ... arg)
{
return (... - arg);//左折叠
}
int main()
{
int s1 = sum(1, 2, 2, 4, 5);//解包:((((1+)2+)3+)4+)5 = 15
double s2 = sum(1.1, 2.2, 3.3, 4.4, 5.5, 6.6);
double s3 = sum(1, 2.2, 3, 4.4, 5);
double s4 = sub1(5, 2, 1, 1);//解包:((((5-)2-)1-)1) = 1
double s5 = sub2(5, 2, 1, 1);//解包:(5-(2-(1-(1)))) = 3
double s6 = sum_strong();//s6 = 0
std::string str1("he");
std::string str2("ll");
std::string str3("o ");
std::string str4("world");
std::string str5 = sum(str1, str2, str3, str4);//str5 = "hello world"
return 0;
}
结构化绑定
用一对包含一个或多个变量的中括号,表示结构化绑定,但是使用结构化绑定时,须用
例子:
/*
* 例子:多值返回
*/
struct S
{
double num1;
long num2;
};
S foo(int arg1, double arg2)
{
double result1 = arg1 * arg2;
long result2 = arg2 / arg1;
return {result1, result2};//返回结构体S对象
};
int main()
{
auto [num1, num2] = foo(10, 20.2);//自动推导num1为double,num2为long
return 0;
}
允许非类型模板参数进行常量计算
非类型模板参数可传入类的静态成员
如:
class MyClass
{
public:
static int a;
};
template<int *arg>
void foo() {}
int main()
{
foo<&MyClass::a>();
return 0;
}
条件分支语句初始化
在
如:
template<long value>
void foo(int &ok)
{
if constexpr (ok = 10; value > 0)
{
}
}
int main()
{
int num = 0;
if(int i = 0; i == 0)
{
}
foo<10>(num);
switch(int k = 10; k)
{
case 0:break;
case 1:break;
default:break;
}
return 0;
}
聚合初始化
在初始化对象时,可用花括号进行对其成员进行赋值
如:
struct MyStruct1
{
int a;
int b;
};
struct MyStruct2
{
int a;
MyStruct1 ms;
};
int main()
{
MyStruct1 a{10};
MyStruct2 b{10, 20};
MyStruct2 c{1, {}};
MyStruct2 d{ {}, {}};
MyStruct2 e{ {}, {1, 2}};
return 0;
}
嵌套命名空间
简化多层命名空间的写法
如:
//传统写法
namespace A
{
namespace B
{
namespace C
{
};
};
};
//新写法
namespace A::B::C
{
};
lambda 表达式捕获*this 的值
如:
struct MyStruct {
double ohseven = 100.7;
auto f() {
return [this] {
return [*this] {
this->ohseven = 200.2;//错误,只读变量不可赋值
return ohseven;//正确
};
}();
}
auto g() {
return []{
return [*this]{};//错误,外层lambda表达式没有捕获this
}();
}
};
枚举[ 类] 对象的构造
可以给枚举
如:
enum MyEnum { value };
MyEnum me {10};//错误:不能用int右值初始化MyEnum类型对象
enum byte : unsigned char { };
byte b { 42 }; //正确
byte c = { 42 }; //错误:不能用int右值初始化byte类型对象
byte d = byte{ 42 }; //正确,其值与b相等
byte e { -1 }; //错误:常量表达式-1不能缩小范围为byte类型
struct A { byte b; };
A a1 = { { 42 } }; //错误:不能用int右值初始化byte类型对象
A a2 = { byte{ 42 } }; //正确
void f(byte);
f({ 42 }); //错误:无类型说明符
enum class Handle : unsigned int { value = 0 };
Handle h { 42 }; //正确
十六进制单精度浮点数字面值
以
如:
int main()
{
float value = 0x1111f;
return 0;
}
基于对齐内存的动态内存分配
谈到动态内存分配,少不了
函数原型:
void* operator new(std::size_t size, std::align_val_t alignment);
void* operator new[](std::size_t size, std::align_val_t alignment);
void operator delete(void*, std::size_t size, std::align_val_t alignment);
细化表达式的计算顺序
为了支持泛型编程和重载运算符的广泛使用,新特性将计算顺序进行的细化
如以下争议代码段:
#include<map>
int main()
{
std::map<int, int> tmp;
//对于std::map的[]运算符重载函数,在使用[]新增key时,std::map就已经插入了一个新的键值对
tmp[0] = tmp.size();//此处不知道插入的是{0, 0}还是{0, 1}
return 0;
}
为了解决该情况,新计算顺序规则为:
- 后缀表达式从左到右求值。这包括函数调用和成员选择表达式。
- 赋值表达式从右向左求值。这包括复合赋值。
- 从左到右计算移位操作符的操作数。
模板类的模板参数自动推导
定义模板类的对象时,可以不指定模板参数,但必须要在构造函数中能推导出模板参数
如:
template<class T> struct A {
explicit A(const T&, ...) noexcept {} // #1
A(T&&, ...){} // #2
};
int i;
A a1 = { i, i }; //错误,不能根据#1推导为右值引用,也不能通过#1实现复制初始化
A a2{i, i}; //正确,调用#1初始化成功,a2推导为A<int>类型
A a3{0, i}; //正确,调用#2初始化成功,a2推导为A<int>类型
A a4 = {0, i}; //正确,调用#2初始化成功,a2推导为A<int>类型
template<class T> A(const T&, const T&) -> A<T&>; // #3
template<class T> explicit A(T&&, T&&) -> A<T>; // #4
A a5 = {0, 1}; //错误,#1和#2构造函数结果相同(即冲突)。根据#3推导为A<int&>类型
A a6{0, 1}; //正确,通过#2推断为A<int>类型
A a7 = {0, i}; //错误,不能将非静态左值引用绑定到右值。根据#3推导为A<int&>类型
A a8{0, i}; //错误,不能将非静态左值引用绑定到右值。根据#3推导为A<int&>类型
template<class T>
struct B {
template<class U>
using TA = T;//定义别名
template<class U>
B(U, TA<U>);//构造函数
};
B b{(int*)0, (char*)0}; //正确,推导为B<char *>类型
简化重复命名空间的属性列表
如:
[[ using CC: opt(1), debug ]] void f() {}
//作用相同于 [[ CC::opt(1), CC::debug ]] void f() {}
不支持、非标准的属性
在添加属性列表时,编译器会忽略不支持的非标准的属性,不会发出警告和错误。
改写与继承构造函数
在类的继承体系中,构造函数的自动调用是一个令人头疼的问题。新特性引入继承与改写构造函数的用法。
例:
#include<iostream>
struct B1
{
B1(int) { std::cout << "B1" << std::endl; }
};
struct D1 : B1 {
using B1::B1;//表示继承B1的构造函数
};
D1 d1(0); //正确,委托基类构造函数进行初始化,调用B1::B1(int)
宏
__has_include
判断有没有包含某文件
如:
int main()
{
#if __has_include(<cstdio>)
printf("hehe");
#endif
#if __has_include("iostream")
std::cout << "hehe" << std::endl;
#endif
return 0;
}
属性
fallthrough
用于
如:
int main()
{
int ok1, ok2;
switch (0)
{
case 0:
ok1 = 0;
[[fallthrough]];
case 1:
ok2 = 1;
[[fallthrough]];
}
return 0;
}
nodiscard
可用于类声明、函数声明、枚举声明中,表示函数的返回值没有被接收,在编译时会出现警告。
如:
[[nodiscard]] class A {}; //该属性在这其实没用
[[nodiscard]] enum class B {}; //该属性在这其实没用
class C {};
[[nodiscard]] int foo()
{ return 10; }
[[nodiscard]] A func1() { return A(); }
[[nodiscard]] B func2() { return B(); }
[[nodiscard]] C func3() { return C(); }
int main()
{
foo();//warning: ignoring return value
func1();//warning: ignoring return value
func2();//warning: ignoring return value
func3();//warning: ignoring return value
return 0;
}
maybe_unused
可用于类、typedef、变量、非静态数据成员、函数、枚举或枚举值中。用于抑制编译器对没用实体的警告。即加上该属性后,对某一实体不会发出“没有用”的警告。
用法例子:
[[maybe_unused]] class A {};
[[maybe_unused]] enum B {};
[[maybe_unused]] int C;
[[maybe_unused]] void fun();
数据类型
std::variant
std::optional
如果通过
std::any
一个类型安全的可以保存任何值的容器
std::string_view
仅仅保存了原始字符串地址和长度等信息。 在很多情况下,我们只是临时处理字符串,本不需要对原始字符串的一份拷贝。
使用
需要注意的是,
当原始的字符串已经销毁,则不能再调用