Skip to content

Latest commit

 

History

History
970 lines (700 loc) · 19.5 KB

C++005:C++ Lambda表达式详解.md

File metadata and controls

970 lines (700 loc) · 19.5 KB

1. Lambda表达式的基本概念

什么是Lambda表达式?

Lambda表达式是C++11引入的一种匿名函数,允许在代码中直接定义一个函数对象,而无需显式定义一个命名函数或函数对象类。

语法结构

[捕获列表](参数列表) mutable 异常规范 -> 返回类型 { 函数体 }

示例代码

#include <iostream>

int main() {
    // 定义一个简单的Lambda表达式
    auto lambda = []() { std::cout << "Hello, Lambda!" << std::endl; };

    // 调用Lambda表达式
    lambda();

    return 0;
}

解释

  • []:捕获列表,表示Lambda表达式可以访问的外部变量。
  • ():参数列表,类似于普通函数的参数列表。
  • {}:函数体,定义Lambda表达式的行为。

2. Lambda表达式的语法

捕获列表(Capture List)

捕获列表用于指定Lambda表达式可以访问的外部变量。

捕获方式

  • []:不捕获任何变量。
  • [=]:默认值捕获,捕获所有外部变量,但捕获的变量是只读的。
  • [&]:默认引用捕获,捕获所有外部变量,捕获的变量是可修改的。
  • [x]:值捕获变量x
  • [&x]:引用捕获变量x
  • [x, &y]:值捕获x,引用捕获y

示例代码

#include <iostream>

int main() {
    int x = 42;
    int y = 100;

    // 值捕获x,引用捕获y
    auto lambda = [x, &y]() {
        std::cout << "x: " << x << ", y: " << y << std::endl;
        y = 200; // 修改y的值
    };

    lambda(); // 输出x: 42, y: 100
    std::cout << "After lambda: y = " << y << std::endl; // 输出200

    return 0;
}

捕获的默认行为

  • [=]:默认值捕获,所有捕获的变量是只读的。
  • [&]:默认引用捕获,所有捕获的变量是可修改的。

捕获的变量生命周期

捕获的变量生命周期必须长于Lambda表达式的生命周期,否则会导致未定义行为。

参数列表(Parameter List)

参数列表用于定义Lambda表达式的参数。

示例代码

#include <iostream>

int main() {
    // 定义带参数的Lambda表达式
    auto lambda = [](int a, int b) { return a + b; };

    std::cout << "Sum: " << lambda(3, 4) << std::endl; // 输出7

    return 0;
}

可变参数(Variadic Parameters)

Lambda表达式支持可变参数,使用折叠表达式可以处理多个参数。

示例代码

#include <iostream>

int main() {
    // 定义可变参数的Lambda表达式
    auto lambda = [](auto... args) {
        return (args + ...); // 折叠表达式,计算所有参数的和
    };

    std::cout << "Sum: " << lambda(1, 2, 3, 4) << std::endl; // 输出10

    return 0;
}

可变性(Mutable)

mutable关键字允许修改值捕获的变量。

示例代码

#include <iostream>

int main() {
    int x = 42;

    // 值捕获x,可变Lambda
    auto lambda = [x]() mutable {
        x = 100; // 允许修改捕获的值
        std::cout << "x: " << x << std::endl;
    };

    lambda(); // 输出x: 100
    std::cout << "Original x: " << x << std::endl; // 输出42,原始x未改变

    return 0;
}

异常规范(Exception Specification)

Lambda表达式可以使用noexcept指定不抛出异常。

示例代码

#include <iostream>

int main() {
    // 定义不抛出异常的Lambda表达式
    auto lambda = []() noexcept { std::cout << "No exception" << std::endl; };

    lambda();

    return 0;
}

尾随返回类型(Trailing Return Type)

Lambda表达式可以显式指定返回类型,也可以自动推导返回类型。

示例代码

#include <iostream>

int main() {
    // 显式指定返回类型
    auto lambda = [](int a, int b) -> int { return a + b; };

    std::cout << "Sum: " << lambda(3, 4) << std::endl; // 输出7

    return 0;
}

3. Lambda表达式的类型

Lambda表达式的类型是匿名的

Lambda表达式的类型是编译器生成的匿名类型,无法直接使用。

如何获取Lambda的类型

  • 使用decltype获取Lambda表达式的类型。
  • 使用std::function进行类型擦除。

示例代码

#include <iostream>
#include <functional>

int main() {
    auto lambda = [](int x) { return x * 2; };

    // 使用decltype获取Lambda的类型
    decltype(lambda) lambda2 = lambda;

    // 使用std::function进行类型擦除
    std::function<int(int)> func = lambda;

    std::cout << "Lambda result: " << lambda(42) << std::endl; // 输出84
    std::cout << "Lambda2 result: " << lambda2(42) << std::endl; // 输出84
    std::cout << "Function result: " << func(42) << std::endl; // 输出84

    return 0;
}

Lambda表达式的类型擦除

std::function可以用于存储任意可调用对象,包括Lambda表达式。

示例代码

#include <iostream>
#include <functional>

int main() {
    std::function<int(int)> func = [](int x) { return x * 2; };

    std::cout << "Function result: " << func(42) << std::endl; // 输出84

    return 0;
}

4. Lambda表达式的捕获机制

值捕获(Capture by Value)

值捕获的变量是只读的,使用mutable可以修改捕获的值。

示例代码

#include <iostream>

int main() {
    int x = 42;

    // 值捕获x,不可变Lambda
    auto lambda = [x]() {
        // x = 100; // 错误:x是只读的
        std::cout << "x: " << x << std::endl;
    };

    lambda(); // 输出x: 42

    return 0;
}

引用捕获(Capture by Reference)

引用捕获的变量是可修改的,需要注意变量的生命周期。

示例代码

#include <iostream>

int main() {
    int x = 42;

    // 引用捕获变量x
    auto lambda = [&x]() {
        x = 100; // 修改x的值
        std::cout << "x: " << x << std::endl;
    };

    lambda(); // 输出x: 100
    std::cout << "Original x: " << x << std::endl; // 输出100

    return 0;
}

默认捕获(Default Capture)

默认捕获可以捕获所有外部变量,使用[=][&]

示例代码

#include <iostream>

int main() {
    int x = 42;
    int y = 100;

    // 默认值捕获
    auto lambda1 = [=]() { std::cout << "x: " << x << ", y: " << y << std::endl; };

    // 默认引用捕获
    auto lambda2 = [&]() { std::cout << "x: " << x << ", y: " << y << std::endl; };

    lambda1(); // 输出x: 42, y: 100
    lambda2(); // 输出x: 42, y: 100

    return 0;
}

混合捕获(Mixed Capture)

可以同时使用值捕获和引用捕获。

示例代码

#include <iostream>

int main() {
    int x = 42;
    int y = 100;

    // 值捕获x,引用捕获y
    auto lambda = [x, &y]() {
        std::cout << "x: " << x << ", y: " << y << std::endl;
        y = 200; // 修改y的值
    };

    lambda(); // 输出x: 42, y: 100
    std::cout << "After lambda: y = " << y << std::endl; // 输出200

    return 0;
}

捕获this指针

捕获this指针可以访问当前对象的成员变量和成员函数。

示例代码

#include <iostream>

class MyClass {
public:
    void print() {
        auto lambda = [this]() { std::cout << "x: " << x << std::endl; };
        lambda();
    }

private:
    int x = 42;
};

int main() {
    MyClass obj;
    obj.print(); // 输出x: 42

    return 0;
}

捕获动态变量

捕获动态分配的变量需要注意内存管理。

示例代码

#include <iostream>
#include <memory>

int main() {
    auto ptr = std::make_unique<int>(42);

    // 捕获动态变量
    auto lambda = [ptr = std::move(ptr)]() {
        std::cout << "Value: " << *ptr << std::endl;
    };

    lambda(); // 输出Value: 42

    return 0;
}

5. Lambda表达式的使用场景

作为函数参数

Lambda表达式可以作为函数参数传递。

示例代码

#include <iostream>

void execute(std::function<void()> func) {
    func();
}

int main() {
    int x = 42;

    // 将Lambda表达式作为函数参数传递
    execute([x]() { std::cout << "x: " << x << std::endl; });

    return 0;
}

作为返回值

Lambda表达式可以作为函数的返回值。

示例代码

#include <iostream>

std::function<int(int)> createLambda() {
    return [](int x) { return x * 2; };
}

int main() {
    auto lambda = createLambda();

    std::cout << "Result: " << lambda(42) << std::endl; // 输出84

    return 0;
}

在STL算法中使用

Lambda表达式常用于STL算法中。

示例代码

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 使用Lambda表达式遍历vector
    std::for_each(vec.begin(), vec.end(), [](int x) { std::cout << x << " "; });

    return 0;
}

在多线程编程中使用

Lambda表达式可以用于多线程编程。

示例代码

#include <iostream>
#include <thread>

int main() {
    int x = 42;

    // 创建一个线程,使用Lambda表达式
    std::thread t([&x]() {
        std::cout << "Thread: x = " << x << std::endl;
    });

    t.join(); // 等待线程结束

    return 0;
}

在事件驱动编程中使用

Lambda表达式可以用于事件处理。

示例代码

#include <iostream>
#include <functional>

class EventHandler {
public:
    void onEvent(std::function<void()> callback) {
        callback();
    }
};

int main() {
    EventHandler handler;

    // 使用Lambda表达式作为事件回调
    handler.onEvent([]() { std::cout << "Event handled" << std::endl; });

    return 0;
}

在回调函数中使用

Lambda表达式可以用于回调函数。

示例代码

#include <iostream>

void doWork(std::function<void()> callback) {
    std::cout << "Working..." << std::endl;
    callback();
}

int main() {
    int x = 42;

    // 使用Lambda表达式作为回调函数
    doWork([x]() { std::cout << "Callback: x = " << x << std::endl; });

    return 0;
}

6. Lambda表达式的性能

Lambda表达式的开销

Lambda表达式的开销主要来自于捕获变量的存储和调用。

与普通函数和std::function的性能对比

Lambda表达式通常比std::function性能更好,因为std::function涉及类型擦除。

示例代码

#include <iostream>
#include <chrono>
#include <functional>

void normalFunction(int n) {
    for (int i = 0; i < n; ++i) {
        // 空循环
    }
}

int main() {
    const int n = 100000000;

    // 普通函数
    auto start = std::chrono::high_resolution_clock::now();
    normalFunction(n);
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "Normal function: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;

    // Lambda表达式
    start = std::chrono::high_resolution_clock::now();
    auto lambda = [](int n) {
        for (int i = 0; i < n; ++i) {
            // 空循环
        }
    };
    lambda(n);
    end = std::chrono::high_resolution_clock::now();
    std::cout << "Lambda: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;

    // std::function
    start = std::chrono::high_resolution_clock::now();
    std::function<void(int)> func = [](int n) {
        for (int i = 0; i < n; ++i) {
            // 空循环
        }
    };
    func(n);
    end = std::chrono::high_resolution_clock::now();
    std::cout << "std::function: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;

    return 0;
}

捕获变量对性能的影响

捕获变量会增加Lambda表达式的开销,尤其是捕获大量变量时。


7. Lambda表达式的高级特性

递归Lambda

递归Lambda可以使用std::function实现。

示例代码

#include <iostream>
#include <functional>

int main() {
    // 使用std::function实现递归Lambda
    std::function<int(int)> factorial = [&](int n) {
        return n == 0 ? 1 : n * factorial(n - 1);
    };

    std::cout << "Factorial of 5: " << factorial(5) << std::endl; // 输出120

    return 0;
}

闭包(Closure)

闭包是指Lambda表达式捕获的外部变量和函数体的组合。

示例代码

#include <iostream>

int main() {
    int x = 42;

    // 闭包:捕获x并返回x的平方
    auto closure = [x]() { return x * x; };

    std::cout << "Closure result: " << closure() << std::endl; // 输出1764

    return 0;
}

捕获静态变量和全局变量

Lambda表达式可以捕获静态变量和全局变量。

示例代码

#include <iostream>

int globalVar = 100;

int main() {
    static int staticVar = 200;

    // 捕获静态变量和全局变量
    auto lambda = [=]() {
        std::cout << "Global var: " << globalVar << ", Static var: " << staticVar << std::endl;
    };

    lambda(); // 输出Global var: 100, Static var: 200

    return 0;
}

捕获成员变量

Lambda表达式可以捕获类的成员变量。

示例代码

#include <iostream>

class MyClass {
public:
    void print() {
        auto lambda = [this]() { std::cout << "x: " << x << std::endl; };
        lambda();
    }

private:
    int x = 42;
};

int main() {
    MyClass obj;
    obj.print(); // 输出x: 42

    return 0;
}

捕获*this

捕获*this可以捕获当前对象的副本。

示例代码

#include <iostream>

class MyClass {
public:
    void print() {
        auto lambda = [*this]() { std::cout << "x: " << x << std::endl; };
        lambda();
    }

private:
    int x = 42;
};

int main() {
    MyClass obj;
    obj.print(); // 输出x: 42

    return 0;
}

8. Lambda表达式的限制

不能捕获的变量类型

Lambda表达式不能捕获constexpr变量。

不能捕获的变量生命周期

捕获的变量生命周期必须长于Lambda表达式的生命周期。

不能捕获的变量作用域

Lambda表达式不能捕获超出作用域的变量。


9. Lambda表达式与C++标准库

std::function的结合使用

std::function可以存储Lambda表达式。

示例代码

#include <iostream>
#include <functional>

int main() {
    std::function<int(int)> func = [](int x) { return x * 2; };

    std::cout << "Function result: " << func(42) << std::endl; // 输出84

    return 0;
}

std::bind的对比

std::bind可以绑定参数,但不如Lambda表达式灵活。

示例代码

#include <iostream>
#include <functional>

void print(int a, int b) {
    std::cout << "a: " << a << ", b: " << b << std::endl;
}

int main() {
    // 使用std::bind绑定参数
    auto bindFunc = std::bind(print, 1, std::placeholders::_1);

    bindFunc(2); // 输出a: 1, b: 2

    // 使用Lambda表达式
    auto lambda = [](int a, int b) { print(a, b); };

    lambda(1, 2); // 输出a: 1, b: 2

    return 0;
}

std::thread的结合使用

Lambda表达式可以用于多线程编程。

示例代码

#include <iostream>
#include <thread>

int main() {
    int x = 42;

    // 创建一个线程,使用Lambda表达式
    std::thread t([&x]() {
        std::cout << "Thread: x = " << x << std::endl;
    });

    t.join(); // 等待线程结束

    return 0;
}

std::async的结合使用

Lambda表达式可以用于异步编程。

示例代码

#include <iostream>
#include <future>

int main() {
    // 使用std::async和Lambda表达式
    auto future = std::async([]() {
        std::cout << "Async task" << std::endl;
        return 42;
    });

    std::cout << "Result: " << future.get() << std::endl; // 输出42

    return 0;
}

std::unique_ptr的结合使用

Lambda表达式可以捕获std::unique_ptr

示例代码

#include <iostream>
#include <memory>

int main() {
    auto ptr = std::make_unique<int>(42);

    // 捕获std::unique_ptr
    auto lambda = [ptr = std::move(ptr)]() {
        std::cout << "Value: " << *ptr << std::endl;
    };

    lambda(); // 输出Value: 42

    return 0;
}

10. Lambda表达式的未来发展

C++20中的模板Lambda

模板Lambda允许在Lambda表达式中使用模板参数。

示例代码

#include <iostream>

int main() {
    // 模板Lambda
    auto lambda = []<typename T>(T x) { std::cout << "Type: " << typeid(T).name() << ", Value: " << x << std::endl; };

    lambda(42); // 输出int
    lambda(3.14); // 输出double

    return 0;
}

C++23中的Lambda新特性

C++23引入了更多Lambda表达式的新特性,如捕获constexpr变量。


11. Lambda表达式的常见问题

捕获变量的生命周期问题

捕获的变量生命周期必须长于Lambda表达式的生命周期。

捕获变量的作用域问题

捕获的变量必须在Lambda表达式的作用域内。

捕获变量的线程安全问题

捕获的变量在多线程环境中需要考虑线程安全。

捕获变量的性能问题

捕获大量变量会增加Lambda表达式的开销。


12. Lambda表达式的最佳实践

何时使用Lambda表达式

  • 在需要定义简单、临时的函数对象时。
  • 在STL算法中使用。
  • 在多线程编程中使用。

何时避免使用Lambda表达式

  • 当函数逻辑复杂且需要复用时,建议使用命名函数。
  • 当捕获的变量过多时,可能会导致代码可读性下降。

如何优化Lambda表达式的性能

  • 尽量减少捕获的变量数量。
  • 避免使用std::function,除非必须进行类型擦除。

如何避免Lambda表达式的陷阱

  • 确保捕获的变量生命周期长于Lambda表达式的生命周期。
  • 在多线程环境中注意线程安全。

13. Lambda表达式的代码示例

基本Lambda表达式示例

#include <iostream>

int main() {
    auto lambda = []() { std::cout << "Hello, Lambda!" << std::endl; };
    lambda();

    return 0;
}

捕获变量的示例

#include <iostream>

int main() {
    int x = 42;
    auto lambda = [x]() { std::cout << "x: " << x << std::endl; };
    lambda();

    return 0;
}

在STL算法中使用的示例

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::for_each(vec.begin(), vec.end(), [](int x) { std::cout << x << " "; });

    return 0;
}

在多线程编程中使用的示例

#include <iostream>
#include <thread>

int main() {
    int x = 42;
    std::thread t([&x]() { std::cout << "Thread: x = " << x << std::endl; });
    t.join();

    return 0;
}

递归Lambda示例

#include <iostream>
#include <functional>

int main() {
    std::function<int(int)> factorial = [&](int n) {
        return n == 0 ? 1 : n * factorial(n - 1);
    };

    std::cout << "Factorial of 5: " << factorial(5) << std::endl;

    return 0;
}

14. Lambda表达式的实现原理

Lambda表达式的编译器实现

编译器会将Lambda表达式转换为一个匿名的闭包类,并在其中生成一个operator()重载。

闭包类的生成

闭包类包含捕获的变量,并重载operator()

捕获变量的存储方式

捕获的变量存储在闭包类的成员变量中。

调用操作符的重载

闭包类重载operator(),用于调用Lambda表达式。


15. Lambda表达式的相关标准

C++11中的Lambda表达式

C++11引入了Lambda表达式。

C++14中的Lambda表达式

C++14引入了泛型Lambda。

C++17中的Lambda表达式

C++17引入了捕获*this

C++20中的Lambda表达式

C++20引入了模板Lambda。

C++23中的Lambda表达式

C++23引入了更多Lambda表达式的新特性。