C++14 新特性

C++14 新特性

  • C++14 新特性
    • 变量模板
    • 通用lambda表达式
    • 常量表达式
    • 二进制字面量
    • 数字分隔符
    • 数组大小自动推导
    • [[deprecated]]标记
    • make_unique
    • exchange
    • integer_sequence
    • constexpr函数的扩展
    • 变长参数模板的扩展
    • 小结

C++14 新特性

C++14并没有太大的改动,就连官方说明中也指出,C++14相对于C++11来说是一个比较小的改动,但是在很大程度上完善了C++11,所以可以说C++14就是在C++11标准上的查漏补缺。

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;
}

运行结果:

C++14 新特性_第1张图片

如果想在类内部定义变量模板,那么需要定义静态变量,同时也可以对模板变量进行特化。

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中新增了一些常量表达式的规则:

  1. 函数可以被声明为常量表达式,只要函数满足以下条件:
    • 函数的返回值类型是字面类型(literal type)
    • 函数体只包含符合常量表达式要求的语句
  2. 可以使用if和switch语句,只要它们的条件表达式是常量表达式,并且语句体也是符合常量表达式要求的语句。
  3. 可以使用循环语句,只要循环次数是常量表达式。
  4. 可以使用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之后,就可以对数字添加分隔符了,这使得大数字的可读性变得更高了。

比如下面两个定义1亿的方式,第二个明显会比第一个可读性高很多。

int main()
{
    // 一亿
    int val1 = 100000000;
    int val2 = 100'000'000;
    std::cout << val1 << " " << val2 << std::endl;
    return 0;
}

注意,数字分隔符并不会对数字本身有任何的影响,只是对数字可读性的增强。

数组大小自动推导

在C++14中,可以使用auto关键字和初始化列表来实现数组大小的自动推导。具体来说,可以使用以下语法:

auto arr = {1, 2, 3, 4}; // 自动推导为std::initializer_list

在这个例子中,编译器会自动推导出arr的类型为std::initializer_list,而数组的大小也会自动根据初始化列表的元素个数进行推导。因此,上述代码等价于下面的代码:

int arr[] = {1, 2, 3, 4}; // 数组大小为4

需要注意的是,这种自动推导方式只适用于静态数组,而对于动态数组来说,还需要使用new运算符手动分配内存。另外,由于std::initializer_list是一个轻量级的容器,因此它的性能可能不如普通数组。

[[deprecated]]标记

C++14引入了deprecated标记符,它可以用来标记有此属性的名字或实体被弃用,被弃用了以后你仍然可以使用,但是编译时会有警告信息。所以你在新版本库中弃用了某个函数,就可以使用deprecated告诉用户尽量不要使用。

其语法如下,第一个只是告诉编译器我们弃用了这个名字,第二个是可以传入一个字面量信息,此信息会在Warning时展示出来:

[[deprecated]]
[[deprecated( "A string" )]]

弃用 class & struct & union & enum

class & struct & union & enum 的弃用属性声明方式如下,都是放在中间:

class [[deprecated]] C{};
struct [[deprecated]] S{};
union  [[deprecated]] U{};
enum [[deprecated]] E{A};

弃用别名 typedef & using:

[[deprecated]] typedef wchar_t* WSTR;
using STR [[deprecated]] = char*;

注意using和typedef声明弃用属性时位置是不一样的。

弃用变量(包括静态成员和非静态成员)

[[deprecated]] int a;
class C{
public:
    [[deprecated]] int b;
    [[deprecated]] static int c;
};

弃用函数

声明如下:

[[deprecated]]
void Func()
{
    std::cout << "hello world!" << std::endl;
}

int main()
{
    Func();
    return 0;
}

编译的时候会有警告信息:

在这里插入图片描述

弃用命名空间

声明如下:

namespace [[deprecated]] NS
{
    void func();
}

弃用枚举项

声明如下,弃用谁在其后面声明。

enum E{A,B,C[[deprecated]]};

我们这里只弃用了枚举项C,没有弃用A和B。

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 
#include 

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 
#include 
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类型整数的序列。

std::make_integer_sequence模板函数可以用来创建一个整数序列。它接受一个整数类型和一个整数序列长度作为参数,并返回一个std::integer_sequence对象。例如,std::make_integer_sequence将返回一个包含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 
#include 

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中:

  1. constexpr修饰变量,要求变量必须是可以在编译器推导出来;
  2. constexpr修饰函数(其实就是修饰函数返回值),除了可以包含 using 指令、typedef 语句以及 static_assert 断言外,只能包含一条 return 返回语句;
  3. constexpr同时可以修饰构造函数,但是也会要求使用这个构造函数时,可以在编译器就把相关的内容全部推导出来。

C++14中对constexpr函数的扩展主要包括以下几个方面:

  1. 放宽了对constexpr函数的限制:在C++11中,constexpr函数只能包含一些简单的语句,比如赋值语句和return语句,而在C++14中,constexpr函数可以包含一些复杂的语句,比如if语句和循环语句。
  2. 允许constexpr函数调用非constexpr函数:在C++11中,constexpr函数只能调用其他constexpr函数,而在C++14中,constexpr函数可以调用非constexpr函数,只要这些函数的返回值可以在编译时确定。
  3. 允许constexpr函数返回void类型:在C++11中,constexpr函数必须返回一个常量表达式,而在C++14中,constexpr函数可以返回void类型,只要函数体中的语句都是常量表达式。
  4. 允许constexpr函数有多个参数:在C++11中,constexpr函数只能有一个参数,而在C++14中,constexpr函数可以有多个参数,只要这些参数都是常量表达式。
  5. 允许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是一个可变数量的模板参数。在函数体中,使用了折叠表达式来将所有参数输出到标准输出。

小结

C++14的内容还是比较少的,但是有一些特性还是非常实用的,比如数字分隔符和lambda表达式的一些特性扩展,在平时写代码中经常会用到的。

你可能感兴趣的:(C++,c++,C++14)