C++14
$14$ 是 $11$ 的进一步优化和完善版本,它引入了一些新的特性和对现有功能的改进
泛型 $lambda$
- 参数推导(
auto
) - 参数初始化后捕捉(可以在[]对某个新参数进行赋值)
auto f = [x = 3](auto y, auto z) {
return x + y + z;
};
std::cout << f(1, 3.14);
- 好处是可以支持移动捕获,
C++11
的lambda
智能使用引用捕获和赋值捕获
std::unique_ptr<A> ptr(new A);
auto f = [p = std::move(ptr)] {};
变量模板
class A {
public:
explicit A(const double &x) {this->x = x;}
double x;
};
template<class T>
T t = T(3.1415926);
int main() {
std::cout << t<int> << "\n";
std::cout << t<double> << "\n";
std::cout << t<A>.x << "\n";
}
- 还可以在类中使用
struct Value {
template<class T>
static const T val; // 类中的模板变量必须为静态
};
template<class T>
const T Value::val = {};
// 特化
template<>
const int Value::val<int> = 1;
template<>
const float Value::val<float> = 2.3;
$constexpr$ 限制放宽
在 $11$ 中被引入的 $constexpr$,可以让编译器在编译程序的期间,就将一部分工作完成,不必等到运行期间再做。但 $constexpr$ 的限制很严格,这导致它并不好用
- $14$ 中,对 $constexpr$ 的限制放宽了,允许使用
循环、if、switch
等等语句,但是主旨还是一样的,必须基于常量表达式,就可以让其在编译期间就可以计算出全部内容
template<class T>
constexpr T power(T a, T b) {
T res {1};
for (; b; b /= 2, a *= a) {
if (b % 2) {
res *= a;
}
}
return res;
}
二进制变量
C++14 引入了 二进制字面量($binary literal$)这一特性,使得开发者能够直接在代码中以二进制格式书写常量。这对于处理底层数据操作,或者需要与硬件交互的代码来说,提供了更直观和简便的表示方法。
- 可以使用
0b
或0B
前缀来表示二进制数。例如,0b1010
表示二进制的1010
,即十进制的10
- 进制字面量默认表示的是整数(通常是
int
类型)。如果需要表示更大的数,可以通过加上后缀来指定类型,如0b1101L
(表示long
类型的数)或0b1101LL
(表示long long
类型的数)
数字分隔符
该特性允许开发者在数字字面量中使用单引号(’)作为分隔符,从而提高代码中长数字的可读性,尤其是在处理大数字或浮点数时
- 单引号只能在数字字面量中的数字部分使用,不能放在数字的开头、结尾或小数点、指数符号等位置
- 仅用于提高代码的可读性,不会影响实际的数值
constexpr int mod = 1'000'000'007;
返回值 $auto$ 推导
在 $14$ 中,$auto$ 关键字不仅可以用于局部变量的类型推导,还可以用于函数的返回类型推导。这种特性使得 在类型推导上更加灵活,尤其是在编写模板函数和复杂返回类型时,能够提高代码的简洁性和可读性。
auto add(int x, int y) {
return x + y;
}
template<class T, class U>
auto add(T x, U y) {
return x + y;
}
- 这个推导是有限制条件的,如果有多个推导语句,那么多个推导的结果必须一致
template<class T>
auto add(T x) {
if (std::is_integral<T>::value) {
return x + 1;
}
return x + 2.0; //error
}
- 如果没有
return
或者return
为void
类型,那么auto
会被推导为void
- 一旦在函数中看到
return
语句,从该语句推导出的返回类型就可以在函数的其余部分中使用,包括在其他return
语句中
auto f(int n) {
if (n == 1) {
return n; // 推导auto为int
}
return n + f(n - 1); // 返回int + int 类型
}
auto f(int n) {
return n + f(n - 1); // 此时不知道f为什么类型,失败
if (n == 1) {
return n;
}
}
- 不能推导初始化列表。需要显示指定
auto init() {
// return {1, 2, 3};
return std::vector<int>{1, 2, 3};
}
- 虚函数不能使用返回值推。如果你在基类中声明了一个虚函数并使用了
auto
来推导返回类型,编译器就无法确定子类中可能重写的返回类型。返回值不一样那子类重写的就不是那个虚函数。
$[[deprecated]]$ 标记
标记某个函数、类型、变量、枚举值等不推荐使用,并提醒开发人员在未来版本中可能会被删除。其目的是帮助开发者在代码中标识和管理过时的或即将废弃的功能,增强代码的可维护性。
[[deprecated]] void init() {}
[[deprecated]] int x = 0;
int main() {
init(); // 编译时会发出警告,提示该函数已经过时
std::cout << x << "\n"; // 编译时会发出警告,提示该变量已经过时
return 0;
}
[[deprecated("message")]]
语法,可以指定一个自定义的警告信息- 通过合理使用 $[[deprecated]]$,可以提高代码的可维护性,并减少因依赖过时功能而产生的潜在问题。
$std::make\_unique$
用于创建
std::unique_ptr
的函数模板。它提供了一种安全、简洁和高效的方式来创建智能指针unique_ptr
,从而避免了使用new
运算符时可能发生的一些潜在错误。
- 语法
std::make_unique<T>(args...);
T:表示对象类型。
args...:传递给对象构造函数的参数。
// ptr内部维护的是 int 类型的指针,值是2
std::unique_ptr<int> ptr1 = std::make_unique<int>(22);
// ptr内部维护的是 10个int类型元素大小的数组
std::unique_ptr<int[]> ptr2 = std::make_unique<int[]>(10);
$std::shared\_timed\_mutex$ 和 $std::shared\_lock$
$11$ 引入了多线程线程的一些库,但是是没有读写锁的,因此在 $14$ 引入了读写锁的相关实现(头文件
shared_mutex
),其实 $14$ 读写锁也还不够完善,直到 $17$ 读写锁这块才算是完备起来。
$std::exchange$
一个非常有用的标准库函数,它的作用是把第二个值赋值给第一个值,同时返回第一个值的旧值。这个函数通常用于简化代码,尤其是在需要对对象进行修改的同时获取原始值的场景中
- 定义如下:
template <typename T>
T exchange(T& obj, T&& new_value);
- 例
std::string s1 = "hello", s2 = "world";
std::exchange(s1, s2);
std::cout << s1 << ' ' << s2 << "\n";
- 第二个参数是一个万能引用类型,也就是说可以传递一个右值
std::string s1 = "hello", s2 = "world";
std::exchange(s1, std::move(s2));
/*
通过std::move将s2变成右值引用,exchange内部可能会调用移动构造或者移动赋值运算将资源转移
此时s2为空
*/