一、Lambda 表达式:匿名函数的灵活魔力

基础语法与应用场景

Lambda 允许在代码中内联定义匿名函数,替代传统的函数对象和函数指针。

// C++11 基础语法
auto sum = [](int a, int b) -> int { return a + b; };
cout << sum(3, 5); // 输出 8

// 结合算法库使用
vector<int> nums = {2, 4, 6};
sort(nums.begin(), nums.end(), [](int a, int b) {
    return a > b; // 降序排序 → {6,4,2}
});
捕获外部变量

通过捕获列表([])控制对外部变量的访问方式:

  • [x]:值捕获(拷贝)

  • [&x]:引用捕获

  • [=]:捕获所有外部变量的值

  • [&]:捕获所有外部变量的引用

int base = 10;
auto addBase = [base](int x) { return x + base; };
cout << addBase(5); // 输出15

常见陷阱与解决 🚧
  1. 引用捕获导致悬空指针

 int* ptr = new int(42);
 auto lambda = [&ptr]() { cout << *ptr; };
 delete ptr;       // ptr 被释放
 lambda();         // 危险!解引用已释放的内存

解决:优先使用值捕获,或确保引用对象的生命周期。

  1. 默认捕获的潜在风险

 vector<int> data;
 auto lambda = [=]() { data.push_back(1); }; // 错误!data 被值捕获,无法修改

修正:改用引用捕获 [&] 或显式捕获 [&data]

  1. 返回局部 Lambda 的问题

 auto createLambda() {
     int x = 5;
     return [&x]() { cout << x; }; // x 已销毁,返回的 Lambda 行为未定义
 }

解决:改为值捕获 [x]


二、智能指针:告别内存泄漏的救星 🔑

智能指针家族与用法

类型

所有权模型

典型场景

unique_ptr

独占所有权,不可拷贝

替代裸指针,管理单一资源

shared_ptr

共享所有权,引用计数

多个对象共享同一资源

weak_ptr

观察者,不增加引用计数

解决 shared_ptr 循环引用

unique_ptr:轻量级独占指针
unique_ptr<int> ptr1 = make_unique<int>(10);
// unique_ptr<int> ptr2 = ptr1; // 错误!无法拷贝
unique_ptr<int> ptr2 = move(ptr1); // 转移所有权,ptr1 变为 nullptr
shared_ptr:共享所有权
shared_ptr<int> s1 = make_shared<int>(20);
shared_ptr<int> s2 = s1; // 引用计数+1 → 变为2
weak_ptr:打破循环引用
class B;
class A {
public:
    shared_ptr<B> b_ptr; 
    ~A() { cout << "A 析构"; }
};

class B {
public:
    weak_ptr<A> a_ptr; // 使用 weak_ptr 避免循环引用
    ~B() { cout << "B 析构"; }
};

内存管理避坑指南 🚨
  1. 混合使用裸指针和智能指针

 int* raw = new int(5);
 shared_ptr<int> sp(raw);
 // 另一处代码中再次用 raw 构造 shared_ptr → 双重释放!

解决:始终使用 make_shared 或 make_unique

  1. 循环引用导致内存泄漏

 class A { shared_ptr<B> b; };
 class B { shared_ptr<A> a; }; // A 和 B 互相持有 → 引用计数始终≥1

解决:将其中一个成员改为 weak_ptr

  1. 误用 get() 获取裸指针

 shared_ptr<int> sp = make_shared<int>(42);
 int* raw = sp.get();
 delete raw; // 错误!应由 sp 自动管理

建议:除非必要,避免使用 get()


三、自动类型推导与范围循环

auto 关键字:简化代码
auto x = 5;                   // x → int
auto name = "Alice"s;         // name → std::string
auto result = find(nums, 3);  // 自动匹配返回值类型
auto 类型推导注意事项

1. 类型推导可能与预期不符问题auto 会根据初始化表达式 去掉引用和 const 限定符示例

const int x = 5;
auto y = x;       // y 类型是 int(而非 const int!)
auto& z = x;      // z 类型是 const int& (正确保留 const 和引用)

2. 代理对象的陷阱(如 vector<bool>问题:某些容器(如 vector<bool>)返回代理对象,直接用 auto 会误拷贝。示例

vector<bool> vec_bool = {true, false};
auto b = vec_bool[0];    // auto → vector<bool>::reference 的临时副本
b = false;               // 修改不影响原容器!

解决:显式转换为目标类型或直接操作原容器:

// 正确访问 bool 值
bool real_b = static_cast<bool>(vec_bool[0]); 

3. 推导 std::initializer_list 的风险问题auto + 列表初始化可能产生意外的 std::initializer_list

auto nums = {1, 2, 3};   // nums 类型是 initializer_list<int>, 而非 vector!
// nums.size();          // initializer_list 没有 size() 方法!(需转换为容器)

建议:显式声明容器类型:

vector<int> nums = {1, 2, 3};  // 正确方式

范围 for 循环
vector<int> nums = {1,3,5};
for (auto num : nums) {      // 值拷贝遍历 → num 是副本
    cout << num << " ";
}

for (auto& num : nums) {     // 引用遍历 → 可修改元素
    num *= 2;
}
范围循环(Range-based for loop)的典型陷阱

1. 循环中修改容器导致迭代器失效问题:遍历时增/删容器元素会破坏迭代器。

vector<int> vec = {1, 2, 3};
for (auto num : vec) {
    vec.push_back(num * 2); // 危险!可能导致迭代器失效→崩溃
}

解决:先缓存需要修改的内容,或在传统循环中处理迭代器:

vector<int> tmp;
for (auto num : vec) { 
    tmp.push_back(num * 2); 
}
vec.insert(vec.end(), tmp.begin(), tmp.end());

2. 遍历临时对象的生命周期问题问题:循环作用于函数返回的临时容器可能引发未定义行为。

for (auto x : getTemporaryVector()) { 
   // 临时 vector 可能在循环前已被销毁!
}

解决:将临时对象绑定到局部变量:

auto tmp = getTemporaryVector();
for (auto x : tmp) { ... } // 安全

3. 忽视拷贝与引用的性能差异问题:对大对象或容器使用拷贝遍历,导致性能损失。

vector<vector<int>> matrix;
for (auto row : matrix) {  // 拷贝整个 vector<int> → 性能杀手!
    // ...
}

优化:优先使用 const auto& 或 auto&

for (const auto& row : matrix) {  // 仅传递引用,零拷贝
    // 不可修改元素
}
for (auto& row : matrix) {         // 可修改元素
    row.push_back(42); 
}

4. 结构化绑定结合循环时的忽略引用问题:误用值捕获导致容器数据无法修改。

map<string, vector<int>> data;
for (auto [key, vec] : data) {  // vec 是 vector<int> 的拷贝!
    vec.push_back(42);          // 原容器数据不变
}

正确写法

for (auto& [key, vec] : data) {  // 引用捕获
    vec.push_back(42);           // 修改原容器
}

四、右值引用与移动语义:告别不必要的拷贝

基础概念与用法

通过 右值引用(&& 和 移动语义,C++11 允许高效转移资源,减少深拷贝开销。

class DynamicArray {
public:
    // 移动构造函数
    DynamicArray(DynamicArray&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr; // 原对象置空,避免资源重复释放
    }

private:
    int* data_;
    size_t size_;
};

DynamicArray arr1(100);   // 构造函数分配内存
DynamicArray arr2 = std::move(arr1); // 移动构造,资源从 arr1 转移至 arr2

实战场景示例
vector<string> strings;
string largeStr = "超长字符串......";
strings.push_back(std::move(largeStr)); // 移动而非拷贝,largeStr 变为空
避坑指南 🚧
  1. 误用移动后的对象

 string s = "Hello";
 string s2 = std::move(s);
 cout << s[0]; // 未定义行为!s 的内部指针可能已被置空

解决:被移动的对象只能进行析构或重新赋值。

  1. 移动语义的误判

 vector<int> v1 = {1,2,3};
 vector<int> v2 = std::move(v1); // 所有元素内存被整体转移
 cout << v1.size(); // 输出 0,但 v1 仍处于合法但未定义状态

规则:标准库容器的移动操作保证源对象为空且可安全析构。


五、constexpr:编译期计算的威力

编译时求值与常量表达式

constexpr 可在编译期计算值或执行函数,提升运行时性能。

constexpr int factorial(int n) {  // C++11 支持
    return n <= 1 ? 1 : n * factorial(n-1);
}

int main() {
    constexpr int fact5 = factorial(5); // 编译时计算 → 120
    int arr[fact5]; // 合法!数组大小为编译期常量
}
C++17 的 constexpr if

在模板中简化条件判断,消除运行时开销。

template <typename T>
auto printInfo(const T& val) {
    if constexpr (is_integral_v<T>) {
        cout << "整数类型: " << val;
    } else if constexpr (is_floating_point_v<T>) {
        cout << "浮点类型: " << val;
    } else {
        cout << "其他类型";
    }
}
注意事项 ⚠️
  1. 过度复杂的 constexpr 函数

 constexpr int complexCalc(int n) {
     // C++11 中函数体只能包含一条 return 语句,C++14 放宽限制
 }

建议:C++14 开始支持循环和局部变量,合理利用新标准特性。

  1. 静态断言增强编译检查

 static_assert(factorial(5) == 120, "编译期计算错误");

六、结构化绑定(C++17):优雅处理多返回值

高效解构元组与结构体
struct Point { int x; double y; };
Point p = {10, 3.14};
auto [xCoord, yCoord] = p;  // 直接解包成员变量
cout << xCoord;             // 输出 10

// 遍历 map 更简洁
map<string, int> scores = {{"Alice", 90}, {"Bob", 85}};
for (const auto& [name, score] : scores) {
    cout << name << ": " << score << endl;
}

避坑指南 🚧
  1. 忽略引用导致拷贝

 map<string, vector<int>> data;
 for (auto [key, vec] : data) {  // vec 会被拷贝!
     // 修改 vec 不影响原数据
 }

优化:使用 auto& 或 const auto&

for (auto& [key, vec] : data) { ... } // 直接操作原数据
  1. 非聚合类型无法绑定

 class NonAggregate {
 public:
     NonAggregate(int a, int b) : a(a), b(b) {}
 private:
     int a, b;
 };
 auto [a, b] = NonAggregate(1,2); // 错误!成员变量不可访问

条件:仅适用于公有成员均为非静态的 聚合类型


结语

现代 C++ 核心优势
  • 性能优化:右值引用减少拷贝,constexpr 提升编译期计算。

  • 代码简洁:Lambda 简化回调,结构化绑定提升可读性。

避坑总结
  • 智能指针:避免循环引用,优先用 make_shared

  • Lambda:警惕引用捕获导致悬挂指针。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐