C++14 新特性
- C++14 新特性
- 变量模板
- 通用lambda表达式
- 常量表达式
- 二进制字面量
- 数组大小自动推导
- make_unique
- exchange
- integer_sequence
- constexpr函数的扩展
- 变长参数模板的扩展
C++14 新特性
C++14 is a minor but important upgrade over C++11, and largely “completes C++11.”
变量模板
C++14可以定义变量模板:
template<class T>
constexpr T pi = T(3.1415926535897932385L); // variable template
int main()
{
std::cout << pi<double> << std::endl;
std::cout << pi<float> << std::endl;
std::cout << pi<int> << std::endl;
return 0;
}
运行结果:
如果想在类内部定义变量模板,那么需要定义静态变量,同时也可以对模板变量进行特化。
struct Limits
{
template<typename T>
static const T min; // 声明静态成员模板
};
template<typename T>
const T Limits::min = { }; // 定义静态成员模板,全部使用默认值
// 下面三个是模板变量的特化
template<>
const float Limits::min<float> = 4.5;
template<>
const double Limits::min<double> = 5.5;
template<>
const std::string Limits::min<std::string> = "hello";
int main()
{
std::cout << Limits::min<int> << std::endl;//0
std::cout << Limits::min<float> << std::endl;//4.5
std::cout << Limits::min<double> << std::endl;//5.5
std::cout << Limits::min<std::string> << std::endl;//hello
return 0
}
通用lambda表达式
C++14引入了通用lambda表达式,可以使用auto关键字作为参数类型和返回类型,使得lambda表达式更加灵活。
通用lambda表达式的语法如下:
[ captures ] ( auto&&... params ) -> decltype(auto) { body }
其中,captures是lambda表达式的捕获列表,params是lambda表达式的参数列表,decltype(auto)表示返回类型会根据body自动推导出来。
例如,以下代码展示了一个使用通用lambda表达式的例子:
int main()
{
auto add = [](auto x, auto y)
{
return x + y;
};
std::cout << add(1, 2) << std::endl; // 输出 3
std::cout << add(1.5, 2.5) << std::endl; // 输出 4
return 0;
}
在这个例子中,lambda表达式的参数类型和返回类型都使用了auto关键字,使得它可以接受不同类型的参数,并返回相应的结果。
常量表达式
C++14中,常量表达式是指在编译时可以计算出结果的表达式,它可以用于声明常量、数组大小、枚举值等。
C++14中新增了一些常量表达式的规则:
- 函数可以被声明为常量表达式,只要函数满足以下条件:
- 函数的返回值类型是字面类型(literal type)
- 函数体只包含符合常量表达式要求的语句
- 可以使用if和switch语句,只要它们的条件表达式是常量表达式,并且语句体也是符合常量表达式要求的语句。
- 可以使用循环语句,只要循环次数是常量表达式。
- 可以使用lambda表达式,只要它符合常量表达式的要求。
示例:
constexpr int factorial(int n)
{
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main()
{
constexpr int n = 5;
int arr[factorial(n)]; // 使用常量表达式计算数组大小
return 0;
}
在上面的例子中,函数factorial被声明为常量表达式,并用于计算数组arr的大小。由于n是一个编译时常量,因此可以在编译时计算出factorial(n)的值,从而确定数组的大小。
二进制字面量
C++14引入了二进制字面量,允许程序员使用二进制表示法来表示整数值。
二进制字面量以前缀0b或0B开头,后面跟着一串二进制数字。例如,0b101010表示十进制数42。
二进制字面量提供了一种简单而方便的方法来表示位模式,这对于编写低级别的系统代码或进行位运算非常有用。
示例:
int main()
{
int a = 0b101010;
std::cout << a << std::endl; //输出42
return 0;
}
数组大小自动推导
在C++14中,可以使用auto关键字和初始化列表来实现数组大小的自动推导。具体来说,可以使用以下语法:
auto arr = {1, 2, 3, 4}; // 自动推导为std::initializer_list<int>
在这个例子中,编译器会自动推导出arr的类型为std::initializer_list<int>,而数组的大小也会自动根据初始化列表的元素个数进行推导。因此,上述代码等价于下面的代码:
int arr[] = {1, 2, 3, 4}; // 数组大小为4
需要注意的是,这种自动推导方式只适用于静态数组,而对于动态数组来说,还需要使用new运算符手动分配内存。另外,由于std::initializer_list是一个轻量级的容器,因此它的性能可能不如普通数组。
make_unique
C++14中的std::make_unique是一个函数模板,用于创建一个std::unique_ptr对象并将其初始化为一个新对象。它接受一个可变参数列表和一个构造函数的参数列表,用于在创建新对象时传递给构造函数。
make_unique的语法如下:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args);
其中,T是要创建的对象的类型,Args是传递给构造函数的参数列表。make_unique返回一个std::unique_ptr对象,该对象拥有指向新对象的所有权。
使用make_unique可以避免手动创建std::unique_ptr对象并进行new操作,从而避免了内存泄漏和错误的可能性。它还可以提高代码的可读性和简洁性。
下面是一个使用make_unique创建一个动态分配的对象的例子:
#include <memory>
#include <iostream>
class MyClass
{
public:
MyClass(int value) : m_value(value)
{
std::cout << "MyClass constructor called with value: " << m_value << std::endl;
}
~MyClass()
{
std::cout << "MyClass destructor called with value: " << m_value << std::endl;
}
private:
int m_value;
};
int main()
{
auto ptr = std::make_unique<MyClass>(42);
return 0;
}
在这个例子中,我们使用make_unique创建了一个动态分配的MyClass对象,并将其初始化为值42。当程序退出main函数时,指向该对象的指针ptr将被自动销毁,并调用MyClass的析构函数。
需要注意的是,make_unique不能用于创建数组,因为std::unique_ptr不支持动态数组。如果需要创建动态数组,应该使用std::vector或std::array。
exchange
std::exchange是C++14中引入的一个函数模板,它定义在头文件中。这个函数的作用是交换一个对象的值并返回其旧值。
std::exchange的函数原型如下:
template<class T, class U = T>
T exchange(T& obj, U&& new_value);
其中,T是要交换值的对象的类型,obj是要交换值的对象的引用,new_value是新值,U是新值的类型。
这个函数的作用是将obj的值用new_value替换,并返回obj原来的值。这个操作是原子的,所以在多线程环境中使用是安全的。
下面是一个使用std::exchange的例子:
#include <iostream>
#include <utility>
int main()
{
int x = 1;
int y = std::exchange(x, 2);
std::cout << "x = " << x << ", y = " << y << std::endl; //输出x = 2, y = 1
return 0;
}
在这个例子中,我们使用std::exchange将x的值从1替换成2,并将原来的值1赋给了y。
integer_sequence
C++14中的std::integer_sequence是一个模板类,用于创建一个整数序列。它可以用于编写与模板参数数量和类型无关的代码,例如元编程和模板元函数。
std::integer_sequence有两个模板参数:第一个是整数类型(通常是std::size_t),第二个是整数序列的长度。例如,std::integer_sequence<std::size_t, 3>表示一个包含三个std::size_t类型整数的序列。
std::make_integer_sequence模板函数可以用来创建一个整数序列。它接受一个整数类型和一个整数序列长度作为参数,并返回一个std::integer_sequence对象。例如,std::make_integer_sequence<std::size_t, 5>将返回一个包含0到4的std::size_t类型整数的序列。
std::index_sequence是std::integer_sequence的特化版本,其中第一个模板参数固定为std::size_t。它通常用于访问元组中的元素,因为元组中的元素是按照索引顺序存储的。
使用std::integer_sequence和std::make_integer_sequence可以实现可变参数模板的参数展开,例如:
template<typename... Ts>
void foo(Ts... args)
{
bar(std::make_index_sequence<sizeof...(Ts)>{}, args...);
}
template<typename... Ts, std::size_t... Is>
void bar(std::index_sequence<Is...>, Ts... args)
{
// 访问args中的元素,例如:
int x = std::get<Is>(std::make_tuple(args...));
}
在上面的例子中,foo函数接受任意数量和类型的参数,并将它们传递给bar函数。bar函数使用std::index_sequence来访问args中的元素。
#include <iostream>
#include <utility>
template<typename... Ts, std::size_t... Is>
void print_helper(const std::tuple<Ts...>& tpl, std::index_sequence<Is...>)
{
((std::cout << std::get<Is>(tpl) << ' '), ...);
std::cout << '\n';
}
template<typename... Ts>
void print(Ts... args)
{
std::tuple<Ts...> tpl(args...);
print_helper(tpl, std::make_index_sequence<sizeof...(Ts)>());
}
template<typename... Ts>
void foo(Ts... args)
{
print(args...);
}
int main()
{
foo(1, 2.5, "hello"); // 输出:1 2.5 hello
return 0;
}
在上面的例子中,print_helper函数使用std::index_sequence来展开std::tuple中的元素,并将它们打印到控制台上。print函数创建一个std::tuple对象,并使用std::make_index_sequence来创建一个std::index_sequence对象,然后将它们传递给print_helper函数。foo函数使用print函数来打印参数。
constexpr函数的扩展
关键字 constexpr 是在 C++11 中引入的,并在 C++14 中进行了改进。 它表示 constant(常数)表达式。 与 const 一样,它可以应用于变量:如果任何代码试图 modify(修改)该值,将引发编译器错误。 与 const 不同,constexpr 也可以应用于函数和类 constructor(构造函数)。 constexpr 指示值或返回值是 constant(常数),如果可能,将在编译时进行计算,这个在很多时候可以提高程序在运行时的效率。
在C++11中,constexpr要求非常严格,这就导致了constexpr的并不是那么易用。
C++11中:
- constexpr修饰变量,要求变量必须是可以在编译器推导出来;
- constexpr修饰函数(其实就是修饰函数返回值),除了可以包含 using 指令、typedef 语句以及 static_assert 断言外,只能包含一条 return 返回语句;
- constexpr同时可以修饰构造函数,但是也会要求使用这个构造函数时,可以在编译器就把相关的内容全部推导出来。
C++14中对constexpr函数的扩展主要包括以下几个方面:
- 放宽了对constexpr函数的限制:在C++11中,constexpr函数只能包含一些简单的语句,比如赋值语句和return语句,而在C++14中,constexpr函数可以包含一些复杂的语句,比如if语句和循环语句。
- 允许constexpr函数调用非constexpr函数:在C++11中,constexpr函数只能调用其他constexpr函数,而在C++14中,constexpr函数可以调用非constexpr函数,只要这些函数的返回值可以在编译时确定。
- 允许constexpr函数返回void类型:在C++11中,constexpr函数必须返回一个常量表达式,而在C++14中,constexpr函数可以返回void类型,只要函数体中的语句都是常量表达式。
- 允许constexpr函数有多个参数:在C++11中,constexpr函数只能有一个参数,而在C++14中,constexpr函数可以有多个参数,只要这些参数都是常量表达式。
- 允许constexpr函数有局部变量:在C++11中,constexpr函数不能有局部变量,而在C++14中,constexpr函数可以有局部变量,只要这些变量都是常量表达式。
总的来说,C++14中对constexpr函数的扩展使得这种函数更加灵活和实用,可以用于更多的场景,提高代码的可读性和可维护性,但仍然需要在编译期间就可以计算出全部内容。
变长参数模板的扩展
C++14中引入了变长参数模板的扩展,可以使用类似于函数参数的语法来定义模板参数列表。这个特性被称为“参数包扩展”或“参数模板扩展”。
参数模板扩展允许在模板参数列表中使用省略号(…)来表示一个可变数量的模板参数。这些参数被称为“参数包”,可以在模板定义中使用。
例如,下面的代码定义了一个可变参数模板,用于在编译时计算一组数字的总和:
template<typename... Args>
int sum(Args... args)
{
return (args + ...);
}
在这个例子中,省略号表示Args是一个可变数量的模板参数。在函数体中,使用了折叠表达式(fold expression)来计算所有参数的总和。
使用参数模板扩展可以极大地简化代码,特别是在处理不同数量的参数时。例如,可以定义一个可变参数模板来打印任意数量的值:
template<typename... Args>
void print(Args... args)
{
(std::cout << ... << args) << '\n';
}
在这个例子中,省略号表示Args是一个可变数量的模板参数。在函数体中,使用了折叠表达式来将所有参数输出到标准输出。