C++14(正式名称为 ISO/IEC 14882:2014)是继 C++11 后的一个重要增量更新,主要目标是完善 C++11 的特性并修复遗留问题,而非引入大量新概念。它于 2014 年正式发布,被广泛视为 C++11 的"优化补丁"。
C++14 的设计哲学是"小而美",专注于提升开发者体验和代码表达力,为后续的 C++17 奠定了坚实基础。
一、语言特性增强
1. 泛型 Lambda 表达式
C++14 允许 Lambda 参数使用 auto
关键字进行自动类型推导,使 Lambda 表达式具备了类似函数模板的泛型能力。
cpp
// 泛型 Lambda - 可以处理任意类型
auto add = [](auto x, auto y) {
return x + y;
};
// 使用示例
int result1 = add(1, 2); // int + int
double result2 = add(1.5, 2.3); // double + double
std::string result3 = add(std::string("Hello"), std::string(" World")); // string + string
// 等价的函数模板形式
template<typename T, typename U>
auto add_func(T x, U y) -> decltype(x + y) {
return x + y;
}
2. Lambda 捕获初始化(广义捕获)
C++14 引入了广义捕获(generalized capture),允许在捕获列表中直接初始化变量,特别适用于移动语义和复杂表达式。
cpp
#include <memory>
#include <iostream>
// 移动捕获示例
auto ptr = std::make_unique<int>(42);
auto lambda1 = [value = std::move(ptr)] {
return *value;
};
// 此时 ptr 已被移动,lambda1 拥有原始资源
// 表达式捕获
int x = 10;
auto lambda2 = [y = x * 2] {
return y + 1;
}; // y = 20, lambda2() 返回 21
// 复杂初始化
auto lambda3 = [vec = std::vector<int>{1, 2, 3, 4, 5}] {
int sum = 0;
for (int i : vec) sum += i;
return sum;
};
std::cout << lambda1() << std::endl; // 42
std::cout << lambda2() << std::endl; // 21
std::cout << lambda3() << std::endl; // 15
3. constexpr
函数限制放宽
C++14 大幅放宽了 constexpr
函数的限制,允许使用局部变量、循环、条件语句等,使编译时计算更加灵活。
cpp
// C++14 中的 constexpr 函数可以包含复杂逻辑
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 fact5 = factorial(5); // 在编译时计算出 120
// 更复杂的例子:编译时字符串长度计算
constexpr size_t strlen_constexpr(const char* str) {
size_t len = 0;
while (str[len] != '\0') {
++len;
}
return len;
}
constexpr size_t hello_len = strlen_constexpr("Hello"); // 编译时计算出 5
// C++11 vs C++14 对比
// C++11 只能写成递归形式:
// constexpr int factorial_cpp11(int n) {
// return n <= 1 ? 1 : n * factorial_cpp11(n - 1);
// }
4. 函数返回类型自动推导
C++14 允许普通函数使用 auto
关键字自动推导返回类型,编译器会根据 return
语句推断类型。
cpp
// 简单的返回类型推导
auto sum(int a, int b) {
return a + b; // 推导为 int
}
// 复杂类型推导
auto create_vector() {
return std::vector<std::string>{"hello", "world"};
}
// 条件返回(所有分支必须返回相同类型)
auto get_value(bool flag) {
if (flag) {
return 42; // int
} else {
return 0; // int
}
// return 3.14; // 错误!不能混合 int 和 double
}
// 递归函数需要前置声明或明确返回类型
auto fibonacci(int n) -> int { // 需要尾置返回类型
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
// 或者使用传统方式
int fibonacci_traditional(int n) {
if (n <= 1) return n;
return fibonacci_traditional(n-1) + fibonacci_traditional(n-2);
}
5. 变量模板(Variable Templates)
C++14 引入了变量模板,允许定义参数化的变量,特别适用于数学常量和类型特性。
cpp
#include <type_traits>
#include <iostream>
// 数学常量模板
template<typename T>
constexpr T pi = T(3.1415926535897932385);
template<typename T>
constexpr T e = T(2.7182818284590452354);
// 使用示例
float pi_f = pi<float>; // 单精度 π
double pi_d = pi<double>; // 双精度 π
long double pi_ld = pi<long double>; // 扩展精度 π
// 类型特性变量模板(简化 type_traits 使用)
template<typename T>
constexpr bool is_integral_v = std::is_integral<T>::value;
template<typename T>
constexpr bool is_floating_point_v = std::is_floating_point<T>::value;
// 使用对比
// C++11/14 传统方式
static_assert(std::is_integral<int>::value, "int should be integral");
// C++14 变量模板方式(更简洁)
static_assert(is_integral_v<int>, "int should be integral");
void example() {
std::cout << "pi<float> = " << pi<float> << std::endl;
std::cout << "pi<double> = " << pi<double> << std::endl;
std::cout << "is_integral_v<int>: " << is_integral_v<int> << std::endl;
std::cout << "is_floating_point_v<double>: " << is_floating_point_v<double> << std::endl;
}
6. 字面量改进
C++14 引入了两个重要的字面量改进,提升了数值表示的便利性和可读性。
二进制字面量
cpp
// 二进制字面量(0b 或 0B 前缀)
int binary1 = 0b1010; // 10 (十进制)
int binary2 = 0B11110000; // 240 (十进制)
int binary3 = 0b0; // 0
// 实际应用:位掩码
constexpr int READ_PERMISSION = 0b100; // 4
constexpr int WRITE_PERMISSION = 0b010; // 2
constexpr int EXEC_PERMISSION = 0b001; // 1
constexpr int ALL_PERMISSIONS = 0b111; // 7
// 位操作示例
int permissions = READ_PERMISSION | WRITE_PERMISSION; // 0b110 = 6
bool can_read = (permissions & READ_PERMISSION) != 0; // true
数字分隔符
cpp
// 使用单引号作为数字分隔符(不影响数值)
int million = 1'000'000; // 一百万
long long big_num = 123'456'789'012; // 大数字
double precise = 3.141'592'653'589; // 高精度数值
// 二进制中的分隔符
int binary_with_sep = 0b1111'0000'1010'0101; // 更易读的二进制
// 十六进制中的分隔符
int hex_with_sep = 0xFF'EE'DD'CC; // 颜色值等
// 注意:分隔符位置灵活,但建议保持一致性
int good_style = 1'234'567; // 推荐:每三位分隔
int also_valid = 12'34'567; // 合法但不推荐
// int invalid = 1''234; // 错误:连续分隔符
// int invalid2 = '1234; // 错误:开头不能有分隔符
7. decltype(auto)
类型推导
C++14 引入了 decltype(auto)
,结合了 auto
的便利性和 decltype
的精确性,特别适用于完美转发和泛型编程。
cpp
#include <iostream>
#include <vector>
int x = 42;
int& get_ref() { return x; }
int get_value() { return x; }
void demonstrate_decltype_auto() {
// auto vs decltype(auto) 对比
// 情况1:普通变量
auto a1 = x; // a1 类型为 int(值拷贝)
decltype(auto) a2 = x; // a2 类型为 int(值拷贝)
// 情况2:引用表达式
auto b1 = (x); // b1 类型为 int(auto 剥离引用)
decltype(auto) b2 = (x); // b2 类型为 int&(保持引用)
// 情况3:函数返回值
auto c1 = get_ref(); // c1 类型为 int(auto 剥离引用)
decltype(auto) c2 = get_ref(); // c2 类型为 int&(保持引用)
auto d1 = get_value(); // d1 类型为 int
decltype(auto) d2 = get_value(); // d2 类型为 int
// 验证类型
std::cout << "b2 和 x 是同一个对象: " << (&b2 == &x) << std::endl; // true
std::cout << "c2 和 x 是同一个对象: " << (&c2 == &x) << std::endl; // true
}
// 实际应用:完美转发函数模板
template<typename Container>
decltype(auto) get_element(Container&& c, size_t index) {
return std::forward<Container>(c)[index];
}
void perfect_forwarding_example() {
std::vector<int> vec{1, 2, 3, 4, 5};
const std::vector<int> const_vec{1, 2, 3, 4, 5};
// 返回引用,可以修改
get_element(vec, 0) = 100; // OK,返回 int&
// 返回 const 引用,不能修改
// get_element(const_vec, 0) = 100; // 编译错误,返回 const int&
auto val = get_element(const_vec, 0); // OK,拷贝值
}
二、标准库新增与改进
1. 智能指针增强
C++14 补充了 std::make_unique
,与 std::make_shared
形成完整的智能指针创建体系。
cpp
#include <memory>
#include <iostream>
#include <vector>
// make_unique 的使用
auto ptr1 = std::make_unique<int>(42);
auto ptr2 = std::make_unique<std::vector<int>>(10, 5); // 10个元素,值为5
// 对比传统方式
// std::unique_ptr<int> old_way(new int(42)); // 不推荐
// 异常安全性优势
void risky_function(std::unique_ptr<int> p1, std::unique_ptr<int> p2) {
// 函数实现
}
// 潜在问题(C++11):
// risky_function(std::unique_ptr<int>(new int(1)),
// std::unique_ptr<int>(new int(2)));
// 如果第二个 new 抛异常,第一个可能泄漏
// 安全方式(C++14):
void safe_call() {
risky_function(std::make_unique<int>(1),
std::make_unique<int>(2));
// 异常安全,不会泄漏
}
// 数组支持
auto arr = std::make_unique<int[]>(10); // 创建 int[10] 数组
arr[0] = 100;
2. 并发支持增强
C++14 引入了读写锁相关的类,支持更细粒度的并发控制。
cpp
#include <shared_mutex> // C++14 新增
#include <thread>
#include <iostream>
#include <vector>
class ThreadSafeCounter {
private:
mutable std::shared_timed_mutex mutex_;
int value_ = 0;
public:
// 读操作(共享锁)
int get() const {
std::shared_lock<std::shared_timed_mutex> lock(mutex_);
return value_;
}
// 写操作(独占锁)
void increment() {
std::unique_lock<std::shared_timed_mutex> lock(mutex_);
++value_;
}
// 带超时的读操作
bool try_get_for(int& result, std::chrono::milliseconds timeout) const {
if (mutex_.try_lock_shared_for(timeout)) {
result = value_;
mutex_.unlock_shared();
return true;
}
return false;
}
};
void demonstrate_shared_mutex() {
ThreadSafeCounter counter;
// 多个读线程可以并发执行
std::vector<std::thread> readers;
for (int i = 0; i < 5; ++i) {
readers.emplace_back([&counter, i] {
for (int j = 0; j < 10; ++j) {
std::cout << "Reader " << i << ": " << counter.get() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
}
// 写线程(独占访问)
std::thread writer([&counter] {
for (int i = 0; i < 50; ++i) {
counter.increment();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
});
for (auto& t : readers) t.join();
writer.join();
}
3. 编译时整数序列
std::integer_sequence
和相关工具简化了变参模板的展开操作。
cpp
#include <utility>
#include <iostream>
#include <tuple>
#include <array>
// 使用 index_sequence 展开 tuple
template<typename Tuple, std::size_t... Is>
void print_tuple_impl(const Tuple& t, std::index_sequence<Is...>) {
((std::cout << std::get<Is>(t) << " "), ...);
}
template<typename... Args>
void print_tuple(const std::tuple<Args...>& t) {
print_tuple_impl(t, std::index_sequence_for<Args...>{});
std::cout << std::endl;
}
// 使用 integer_sequence 创建编译时数组
template<int... Ints>
constexpr auto make_array(std::integer_sequence<int, Ints...>) {
return std::array<int, sizeof...(Ints)>{{Ints...}};
}
// 生成斐波那契数列(编译时)
template<int N>
constexpr int fibonacci() {
if constexpr (N <= 1) return N;
else return fibonacci<N-1>() + fibonacci<N-2>();
}
template<std::size_t... Is>
constexpr auto make_fibonacci_array(std::index_sequence<Is...>) {
return std::array<int, sizeof...(Is)>{{fibonacci<Is>()...}};
}
void demonstrate_sequences() {
// 打印 tuple
auto t = std::make_tuple(1, 2.5, "hello", 'c');
print_tuple(t); // 输出: 1 2.5 hello c
// 编译时生成数组
constexpr auto fib_array = make_fibonacci_array(std::make_index_sequence<10>{});
// fib_array = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34}
for (int val : fib_array) {
std::cout << val << " ";
}
std::cout << std::endl;
}
4. 实用工具函数
std::exchange
原子性地替换对象的值并返回旧值,常用于移动语义和状态重置。
cpp
#include <utility>
#include <iostream>
#include <vector>
#include <memory>
class Resource {
public:
std::vector<int> data;
Resource(std::initializer_list<int> init) : data(init) {}
// 移动构造函数中使用 exchange
Resource(Resource&& other) noexcept
: data(std::exchange(other.data, {})) {
std::cout << "Resource moved" << std::endl;
}
// 移动赋值运算符
Resource& operator=(Resource&& other) noexcept {
if (this != &other) {
data = std::exchange(other.data, {});
}
return *this;
}
};
void demonstrate_exchange() {
// 基本用法
int x = 42;
int old_x = std::exchange(x, 100); // x = 100, old_x = 42
std::cout << "x = " << x << ", old_x = " << old_x << std::endl;
// 容器交换
std::vector<int> v1{1, 2, 3};
std::vector<int> v2{4, 5, 6, 7};
auto old_v1 = std::exchange(v1, std::move(v2));
// 现在 v1 = {4,5,6,7}, old_v1 = {1,2,3}, v2 为空
// 智能指针重置
auto ptr = std::make_unique<int>(42);
auto old_ptr = std::exchange(ptr, std::make_unique<int>(100));
// ptr 指向新值 100,old_ptr 指向原值 42
}
std::quoted
简化字符串的引号处理,特别适用于包含空格的字符串 I/O。
cpp
#include <iostream>
#include <sstream>
#include <iomanip> // for std::quoted
#include <string>
void demonstrate_quoted() {
std::string text = "Hello World with spaces";
// 不使用 quoted 的问题
std::ostringstream oss1;
oss1 << text;
std::string serialized1 = oss1.str();
std::istringstream iss1(serialized1);
std::string deserialized1;
iss1 >> deserialized1; // 只读取到 "Hello",空格后的内容丢失
std::cout << "Without quoted: \"" << deserialized1 << "\"" << std::endl;
// 使用 quoted 的解决方案
std::ostringstream oss2;
oss2 << std::quoted(text);
std::string serialized2 = oss2.str(); // "Hello World with spaces"
std::istringstream iss2(serialized2);
std::string deserialized2;
iss2 >> std::quoted(deserialized2); // 正确读取完整字符串
std::cout << "With quoted: \"" << deserialized2 << "\"" << std::endl;
}
结果展示:
text
Without quoted: "Hello"
With quoted: "Hello World with spaces"
自定义分隔符和转义字符
cpp
#include <iomanip>
#include <iostream>
#include <sstream>
void default_delimiter()
{
const std::string in = "std::quoted() quotes this string and embedded \"quotes\" too";
std::stringstream ss;
ss << std::quoted(in);
std::string out;
ss >> std::quoted(out);
std::cout << "Default delimiter case:\n"
"read in [" << in << "]\n"
"stored as [" << ss.str() << "]\n"
"written out [" << out << "]\n\n";
}
void custom_delimiter()
{
const char delim{'$'};
const char escape{'%'};
const std::string in = "std::quoted() quotes this string and embedded $quotes$ $too";
std::stringstream ss;
ss << std::quoted(in, delim, escape);
std::string out;
ss >> std::quoted(out, delim, escape);
std::cout << "Custom delimiter case:\n"
"read in [" << in << "]\n"
"stored as [" << ss.str() << "]\n"
"written out [" << out << "]\n\n";
}
int main()
{
default_delimiter();
custom_delimiter();
}
结果展示:
text
Default delimiter case:
read in [std::quoted() quotes this string and embedded "quotes" too]
stored as ["std::quoted() quotes this string and embedded \"quotes\" too"]
written out [std::quoted() quotes this string and embedded "quotes" too]
Custom delimiter case:
read in [std::quoted() quotes this string and embedded $quotes$ $too]
stored as [$std::quoted() quotes this string and embedded %$quotes%$ %$too$]
written out [std::quoted() quotes this string and embedded $quotes$ $too]
三、实际应用示例
让我们通过一个综合示例来展示 C++14 的多个特性如何协同工作:
cpp
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <utility>
#include <type_traits>
// 变量模板:类型特性简化
template<typename T>
constexpr bool is_numeric_v = std::is_arithmetic<T>::value;
// 泛型 Lambda 与 constexpr 结合
template<typename Container>
auto process_container(Container&& container) {
// 使用泛型 Lambda 进行数据处理
auto processor = [](auto&& item) {
if constexpr (is_numeric_v<std::decay_t<decltype(item)>>) {
return item * 2; // 数值类型翻倍
} else {
return item; // 非数值类型保持不变
}
};
// 使用 decltype(auto) 完美转发返回类型
auto transform_func = [&processor](auto&& c) -> decltype(auto) {
std::transform(c.begin(), c.end(), c.begin(), processor);
return std::forward<decltype(c)>(c);
};
return transform_func(std::forward<Container>(container));
}
// 使用 make_unique 和移动捕获的资源管理类
class DataProcessor {
private:
std::unique_ptr<std::vector<int>> data_;
public:
DataProcessor(std::initializer_list<int> init)
: data_(std::make_unique<std::vector<int>>(init)) {}
// 使用 Lambda 捕获初始化创建处理器
auto create_multiplier(int factor) {
return [data = data_.get(), factor](size_t index) -> int {
if (index < data->size()) {
return (*data)[index] * factor;
}
return 0;
};
}
// 函数返回类型自动推导
auto get_data() const {
return data_.get();
}
};
int main() {
// 使用数字分隔符和二进制字面量
constexpr int large_number = 1'000'000;
constexpr int binary_mask = 0b1111'0000;
std::cout << "Large number: " << large_number << std::endl;
std::cout << "Binary mask: " << binary_mask << std::endl;
// 测试容器处理
std::vector<int> numbers{1, 2, 3, 4, 5};
auto processed = process_container(numbers);
std::cout << "Processed numbers: ";
for (const auto& num : processed) {
std::cout << num << " ";
}
std::cout << std::endl;
// 测试数据处理器
DataProcessor processor{10, 20, 30, 40, 50};
auto multiplier = processor.create_multiplier(3);
std::cout << "Multiplied values: ";
for (size_t i = 0; i < 5; ++i) {
std::cout << multiplier(i) << " ";
}
std::cout << std::endl;
return 0;
}