22、【C++】C++14常用特性

目录

一、核心语言特性

1.1 泛型lambda表达式(Generic Lambda)

1.1.1 auto参数实现泛型

C++14允许lambda的参数使用auto,实现泛型匿名函数:

// 泛型lambda,可接受任意类型参数
auto add = [](auto x, auto y) {
    return x + y;
};

int main() {
    int sum1 = add(1, 2); // int版本,返回3
    double sum2 = add(1.5, 2.5); // double版本,返回4.0
    std::string sum3 = add(std::string("a"), std::string("b")); // string版本,返回"ab"
    return 0;
}

编译器会为不同参数类型实例化多个lambda版本,类似函数模板。

1.1.2 泛型lambda与函数对象的对比

泛型lambda本质是带有模板operator()的函数对象,但语法更简洁:

// C++11:需手动定义函数对象
struct Add {
    template <typename T, typename U>
    auto operator()(T x, U y) const -> decltype(x + y) {
        return x + y;
    }
};
Add add;

// C++14:泛型lambda等价于上述函数对象
auto add = [](auto x, auto y) { return x + y; };
1.1.3 在标准算法中的应用

泛型lambda简化算法的通用化调用:

#include <vector>
#include <algorithm>

int main() {
    std::vector<int> v1 = {1, 2, 3};
    std::vector<double> v2 = {1.1, 2.2, 3.3};

    // 泛型lambda遍历不同类型容器
    auto print = [](const auto& container) {
        for (const auto& elem : container) {
            std::cout << elem << " ";
        }
        std::cout << std::endl;
    };

    print(v1); // 打印int容器
    print(v2); // 打印double容器
    return 0;
}

1.2 函数返回类型推导(Return Type Deduction)

1.2.1 非模板函数的返回类型推导

C++14允许非模板函数省略返回类型,编译器根据return语句推导:

// 自动推导返回类型为int
auto add(int a, int b) {
    return a + b;
}

// 推导为double
auto multiply(double a, double b) {
    return a * b;
}

限制:函数体内必须有return语句,且所有return语句返回同一类型。

1.2.2 与尾置返回类型的配合

对于复杂返回类型(如模板函数),可结合尾置返回类型:

template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { // C++11/14
    return a + b;
}

template <typename T, typename U>
auto add(T a, U b) { // C++14可省略尾置返回类型
    return a + b;
}
1.2.3 推导规则与限制
  • 推导规则:返回类型为return表达式的类型。
  • 限制
    • 无法推导void返回类型(需显式指定)。
    • 递归函数需至少一个return语句在推导前出现。

1.3 变量模板(Variable Templates)

1.3.1 基本语法与实例化

变量模板允许定义带模板参数的变量:

template <typename T>
constexpr T pi = T(3.14159265358979323846);

int main() {
    double pi_d = pi<double>; // 3.14159...
    float pi_f = pi<float>;   // 3.14159f
    return 0;
}
1.3.2 跨类型常量定义

变量模板可定义适用于多种类型的常量,如不同精度的数学常数:

template <typename T>
constexpr T e = T(2.718281828459045);

template <typename T>
constexpr T golden_ratio = T(1.61803398875);

1.4 constexpr函数扩展

1.4.1 C++14 vs C++11的 constexpr 函数限制
特性 C++11 constexpr函数 C++14 constexpr函数
允许语句 仅允许return语句 允许if、switch、for、while等语句
局部变量 不允许 允许,但必须初始化且为字面量类型
递归 允许 允许
1.4.2 允许循环与条件语句

C++14 constexpr函数可包含循环和条件判断:

// C++14 constexpr函数:计算n的阶乘
constexpr int factorial(int n) {
    if (n <= 1) return 1;
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

constexpr int fact_5 = factorial(5); // 编译期计算,值为120
1.4.3 编译期计算案例

constexpr函数可用于编译期数组初始化:

constexpr int compute_size(int n) {
    return n * 2;
}

int main() {
    constexpr int size = compute_size(5); // 编译期计算size=10
    int arr[size]; // 合法,数组大小为编译期常量
    return 0;
}

1.5 [[deprecated]]属性

1.5.1 标记过时实体

[[deprecated]]属性用于标记过时的函数、类或变量,编译器会发出警告:

[[deprecated]]
void old_function() {
    // 旧实现
}

int main() {
    old_function(); // 编译器警告:'old_function' is deprecated
    return 0;
}
1.5.2 带消息的deprecated

可添加消息说明过时原因和替代方案:

[[deprecated("use new_function instead")]]
void old_function() {}

void new_function() {}

int main() {
    old_function(); // 警告:'old_function' is deprecated: use new_function instead
    return 0;
}

1.6 二进制字面量与数字分隔符

1.6.1 二进制字面量(0b前缀)

C++14引入0b前缀表示二进制数:

constexpr int flag1 = 0b0001; // 1
constexpr int flag2 = 0b0010; // 2
constexpr int combined = flag1 | flag2; // 0b0011 = 3
1.6.2 数字分隔符(')

'可作为数字分隔符,增强大数字的可读性:

constexpr long long population = 7'800'000'000; // 78亿
constexpr double pi = 3'141'592'653'589'793'238'46; // 可读性提升

注意:分隔符不能位于数字开头或结尾,也不能连续使用。

1.7 decltype(auto)

1.7.1 推导规则

decltype(auto)结合decltype和auto的推导规则:

  • 若表达式为x,则推导为decltype(x)
  • 常用于转发函数,保留表达式的值类别(左值/右值)。
1.7.2 在转发函数中的应用
// 转发函数:返回值类型与f(args)一致
template <typename F, typename... Args>
decltype(auto) forward_result(F&& f, Args&&... args) {
    return std::forward<F>(f)(std::forward<Args>(args)...);
}

// 测试:若f返回左值引用,forward_result也返回左值引用
int x = 10;
auto& ref = forward_result([&]() -> int& { return x; });

二、标准库新增特性

2.1 std::make_unique

2.1.1 安全创建unique_ptr

C++14新增std::make_unique,用于安全创建std::unique_ptr

#include <memory>

int main() {
    // C++14:使用make_unique(推荐)
    auto up = std::make_unique<int>(42); // 分配int并初始化为42

    // C++11:直接使用new(不推荐,异常安全问题)
    std::unique_ptr<int> up_old(new int(42));
    return 0;
}
2.1.2 与new的对比

std::make_unique的优势:

  • 异常安全:若构造函数抛出异常,避免内存泄漏。
  • 简洁:无需重复类型名。
  • 避免裸指针:减少直接使用new的机会。

2.2 std::integer_sequence

2.2.1 生成整数序列

std::integer_sequence表示整数序列,配合模板参数包使用:

#include <utility> // for integer_sequence

template <typename T, T... Ints>
void print_sequence(std::integer_sequence<T, Ints...>) {
    ((std::cout << Ints << " "), ...); // C++17折叠表达式
}

int main() {
    print_sequence(std::integer_sequence<int, 1, 2, 3, 4>()); // 输出:1 2 3 4
    return 0;
}
2.2.2 在模板元编程中的应用

生成0到N-1的整数序列,用于展开数组或元组:

// 展开数组:对数组每个元素调用f
template <typename T, size_t N, size_t... Is>
void for_each_array(T (&arr)[N], std::index_sequence<Is...>, auto f) {
    (f(arr[Is]), ...);
}

int main() {
    int arr[] = {1, 2, 3};
    for_each_array(arr, std::make_index_sequence<3>(), [](int x) {
        std::cout << x << " "; // 输出:1 2 3
    });
    return 0;
}

2.3 std::exchange

2.3.1 交换值并返回旧值

std::exchange(obj, new_val)obj赋值为new_val,并返回obj的旧值:

#include <utility>

int main() {
    int x = 10;
    int old_x = std::exchange(x, 20); // x变为20,old_x为10
    return 0;
}
2.3.2 在移动语义中的应用

简化移动构造函数的实现:

class MyString {
public:
    MyString(MyString&& other) noexcept {
        _data = std::exchange(other._data, nullptr); // 转移资源并置空other._data
        _size = std::exchange(other._size, 0);
    }

private:
    char* _data;
    size_t _size;
};

2.4 tuple扩展(按类型访问元素)

C++14允许通过类型而非索引访问tuple元素:

#include <tuple>
#include <string>

int main() {
    std::tuple<int, std::string, double> t(42, "hello", 3.14);
    
    // C++11:按索引访问(不直观)
    int i = std::get<0>(t);
    
    // C++14:按类型访问(需类型唯一)
    std::string s = std::get<std::string>(t);
    double d = std::get<double>(t);
    return 0;
}

2.5 其他标准库改进

2.5.1 std::quoted

std::quoted在IO操作中为字符串添加引号,避免空格分隔问题:

#include <iomanip>
#include <string>

int main() {
    std::string s = "hello world";
    std::cout << std::quoted(s) << std::endl; // 输出:"hello world"
    return 0;
}
2.5.2 constexpr算法

C++14为部分算法添加constexpr版本,支持编译期计算:

#include <algorithm>

constexpr bool is_sorted() {
    constexpr int arr[] = {1, 2, 3, 4};
    return std::is_sorted(std::begin(arr), std::end(arr));
}

constexpr bool sorted = is_sorted(); // 编译期计算为true

三、C++14特性应用场景

3.1 泛型lambda简化算法调用

泛型lambda使算法可直接操作不同类型容器:

#include <vector>
#include <list>
#include <algorithm>

int main() {
    auto sum = [](const auto& container) {
        return std::accumulate(container.begin(), container.end(), 0);
    };

    std::vector<int> v = {1, 2, 3};
    std::list<int> l = {4, 5, 6};
    std::cout << sum(v) + sum(l) << std::endl; // 21
    return 0;
}

3.2 constexpr函数实现编译期数组初始化

constexpr int generate_array(int n) {
    int arr[100];
    for (int i = 0; i < n; ++i) {
        arr[i] = i * i; // 编译期计算平方
    }
    return arr[5]; // 返回25
}

constexpr int val = generate_array(10); // 编译期计算val=25

3.3 变量模板定义跨类型常量

template <typename T>
constexpr T gravity = T(9.80665); // 重力加速度

// 不同单位下的重力加速度
constexpr double g_mps2 = gravity<double>; // m/s²
constexpr float g_fts2 = gravity<float> * 3.28084f; // ft/s²

四、C++14迁移与编译器支持

4.1 代码迁移策略

  • 渐进式迁移:优先使用C++14简化代码,如泛型lambda和返回类型推导。
  • 替换C++11临时方案:用std::make_unique替代自定义工厂函数。
  • 利用 constexpr 扩展:将编译期计算逻辑迁移到constexpr函数。

4.2 主流编译器支持情况

  • GCC:4.9及以上支持大部分C++14特性。
  • Clang:3.4及以上支持。
  • MSVC:Visual Studio 2015及以上支持。

以上内容,全面覆盖了C++14的核心语言特性和标准库改进。C++14作为C++11的增量更新,显著提升了代码简洁性和表达能力,尤其在泛型编程和编译期计算方面。合理应用这些特性可使代码更简洁、高效。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐