C++ 14、17
C++14相对于11并没有太大的改动,14只是对11来说一个比较小的改动,但是在很大程度上完善了11。
一 、C++14 新特性
变量模板
在C++11及之前,模板只针对函数与类。14之后可以使用变量模板
template<class T>
constexpr T Pi = T(3.14159);
template<class T>
T GetCircleArea(T radius)
{
return Pi<T> *radius * radius;
}
模板别名
template<class T,class U>
struct object
{
T _val;
U _val2;
};
template<class T,class U>
using child = object<T, U>;
Lambda
表达式新增的功能
// 1.支持在Lambda表达式中使用auto定义变量类型
auto Lambda = [](auto& param){};
// 2.支持对捕获的变量和引用进行初始化
int x = 10;
auto functor = [&r = x, y = x + 1]() {
return r * y;
};
字面量
二进制字面量:支持使用0b
开头的数字作为表示二进制
int bin = 0b10101001110; // 1358
数字分隔符:支持在数字中使用单引号进行分割
int a = 123'456'789; // 123456789
std::exchange
std::exchange
用于移动语义,可以指定新值替换旧值,并返回旧值。其源码如下:
template<class T, class U = T>
constexpr // since C++20
T exchange(T& obj, U&& new_value)
{
T old_value = std::move(obj);
obj = std::forward<U>(new_value);
return old_value;
}
constexpr
增强
C++程序一般要经历编译,链接,运行三个阶段,非常量表达式只能在程序运行阶段计算出结果,而常量表达式的计算往往在编译阶段,这可以极大的提升程序的运行效率。 constexpr
能让用户显式声明的函数或构造函数在编译期会成为常量表达式 ,C++11中constexpr
只能包含一个返回语句。14中放宽了要求,允许在constexpr
修饰的函数中声明变量、使用循环和条件语句等:
constexpr bool isPrimitive(int number)
{
if (number <= 0)
{
return false;
}
for (int i = 2; i <= sqrt(number) + 1; ++i)
{
if (number % i == 0)
{
return false;
}
}
return true;
}
std::quoted
C++14引入std::quoted
用于给字符串添加双引号
string str = "hello world";
cout << str << endl; // hello world
cout << std::quoted(str) << endl; //"hello world"
二、C++17新特性
if/switch
中定义变量
C++17之前if/switch
语句中不能声明一个临时的变量
std::vector<int> vec = {1, 2, 3, 4};
//C++17
// 将临时变量放到 if 语句内
if (const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) {
*itr = 4;
}
结构化绑定
- 值拷贝方式绑定
auto [key, value] = std::make_pair<int, std::string>(1, "名字");
- 左值引用方式绑定
auto& [key, value] = std::make_pair<int, std::string>(1, "名字");
- 右值引用方式绑定,支持移动语义
auto&& [key, value] = std::make_pair<int, std::string>(1, "名字");
可以用来绑定std::pair
、std::tuple
、std::array
数组和结构体。
std::tuple<int, double, std::string> f() {
return std::make_tuple(1, 2.3, "456");
}
// 结构化绑定tuple
auto [x, y, z] = f();
// 绑定数组
int array[3] = {1,2,3};
auto [a,b,c] = array;
// 绑定结构体,成员属性需要是public
struct FObject {
public:
int x;
int y;
};
auto [a, b] = FObject{ 1,2 };
if constexpr
控制流
constexpr
是将表达式或函数编译为常量结果,如果将这一特性引入条件判断中去,让代码在编译时就完成分支的判断,可以提高程序的效率。
template<typename T>
auto print_type_info(const T& t) {
if constexpr (std::is_integral<T>::value)
{
return t + 1;
} else {
return t + 0.001;
}
}
namespace
嵌套
// C++17之前
namespace orgin{
namespace animal{
namespace bird{
}
}
}
// C++17
namespace orgin::animal::bird{}
新增Attribute
属性
在C++11和14中的Attribute
[[noreturn]] 函数不会返回
[[deprecated]] 函数将弃用的警告
C++17新增[[nodiscard]]
用于强调函数的返回值不会被丢弃,否则会出现编译器警告。
[[nodiscard]] int foo();
void bar() {
foo(); // Warning emitted, return value of a nodiscard function is discarded
}
[[fallthrough]]
,用在switch中提示可以直接落下去,不需要break,让编译期忽略警告。
switch (i) {}
case 1:
xxx; // warning
case 2:
xxx;
[[fallthrough]]; // 警告消除
case 3:
xxx;
break;
}
constexpr lambda
表达式
C++17前lambda
表达式只能在运行时使用,C++17引入了constexpr lambda
表达式,可以用于在编译期进行计算。
constexpr auto lamb = [] (int n) { return n * n; };
在C++17,constexpr
修饰的函数,限制如下:
函数体不能包含汇编语句、goto
语句、label
、try
块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete
等,不能虚函数。
lambda
中的*this
对象副本捕捉
C++17之前lambda
表达式中访问类的对象成员变量只能捕获this,但是这里捕获的是this指针正常情况下可能没问题,但是如果多线程情况下,函数的作用域超过了对象的作用域,对象已经被析构的情况下,在访问该对象就会有很多问题。C++17新增了可以捕获*this,持有的是对象的副本。
struct FObject {
int a;
void ObjectFunc() {
auto lambda = [*this](){
cout << a << endl;
};
}
};
std::optional
更加优雅的写出无返回结果的函数
使用std::optional<T>
来修饰函数返回值,表明这个函数可能不会返回值,T
代表原有的返回类型。
std::map<int, std::string> user_map = { {1,"unreal"} };
std::optional<std::string> FindName(int Id)
{
if (user_map.find(Id) != user_map.end())
{
return user_map[Id];
}
return std::nullopt;
}
// 使用
std::optional<std::string> result = FindName(1);
if (result.has_value())
{
std::string value = *result;
}
内联变量
使用内联变量可以用来初始化静态成员变量。
struct FObject{
inline static const int val = 10;
};
std::variant
C++17添加了std::variant
用来对union
联合体的升级,许多union
不支持的类型,比如std::map
,std::string
都可以在variant
里得到支持。
std::variant<int,double,std::string> varIns;
varIns = "123";
std::cout<< std::get<std::string>(varIns)<<std::endl;
varIns = 10.0;
std::cout<< std::get<double>(varIns)<<std::endl;
std::get会抛出异常,使用std::get_if进行替代
if(auto*str = std::get_if<std::string>(varIns))
{
std::cout<<*str<<std::endl;
}
std::any
C++17引入std::any可以存储任意类型的变量。
struct FObject {
int value = 0b1111;
};
// 使用
std::string str = "unreal";
std::any a1 = str;
std::any a2 = 10;
std::any a3 = FObject();
std::cout << std::any_cast<std::string>(a1) << std::endl;
std::cout << std::any_cast<int>(a2) << std::endl;
std::cout << std::any_cast<FObject>(a3).value << std::endl;
如果转换不成功的话,会抛出bad_any_cast
异常
try
{
std::cout << std::any_cast<int>(a1) << std::endl;
}
catch (std::bad_any_cast& e)
{
std::cout << e.what() << std::endl;
}
可以通过传入参数的指针,转换失败就不会抛出异常
if (auto* ptr = std::any_cast<int>(&a1))
{}
std::apply
std::apply
可以将tuple
作为函数参数传入
void functest(int val, float val2, double val3)
{};
// 调用
std::apply(functest,std::make_tuple(1,2.f,5.234231));
std::string_view
C++17引入std::string_view
协助程序员更高效的使用只读字符串。当遇到需要使用只读字符串,尤其是传入只读字符串作为函数参数时,优先使用std::string_view
。string_view作为只用于显示,**不拥有所有权,并且不可变!**通常内部实现里只有两个数据成员,一个指向数据的指针,二个是数据的size,类型一般为size_t
。在64位系统中一般大小为16个字节,比std::string
小!
以前的写法
void printstr(const std::string& str)
{}
// 方法1
std::string str("unreal");
printstr(str);
// 方法2
const char* str2 = "unity";
printstr(str2);
// 方法3
printstr("cocos");
以上方法方法3的性能开销最大,1和2的开销差不多。方法3传入的是一个纯右值字符串字面量,会创建出一个临时std::string
对象,而const T&
类型可以接受一个纯右值引用,将该变量的生命周期延续到函数结束。
但使用const std::string&
容易造成一些bug,比如悬垂引用:
const std::string& printstr(const std::string& str)
{
return str;
}
auto& str = printstr("unreal");
在函数调用中绑定到函数形参的临时变量,生命周期只持续到函数结尾。如果函数返回一个生命周期长于表达式的引用,那它将会成为一个悬垂引用!
现在的写法。现在使用std::string_view
替代const std::string&
作为字符串的显示。
void printstr(std::string_view str)
{
std::cout<<str<<std::endl;
}
std::string str("unreal");
printstr(str);
const char* str2 = "unity";
printstr(str2);
printstr("cocos");