跳到主要内容

C++ std::function

什么是 std::function?

std::function 是 C++11 引入的一个模板类,它是一个通用的函数包装器,可以存储、复制和调用任何可调用对象(如函数、lambda表达式、绑定表达式或其他函数对象),只要这些对象的调用特征(返回类型和参数类型)符合模板参数的要求。

简单来说,std::function 是一个函数容器,可以存储普通函数、成员函数、函数指针、lambda表达式等多种可调用实体。它属于 C++ 标准库的函数对象(Functor)部分,定义在 <functional> 头文件中。

基本语法

cpp
#include <functional>

// 定义一个 std::function 对象
std::function<返回类型(参数类型列表)> 函数对象名;

std::function 的模板参数是一个函数类型,指定了函数的签名(返回类型和参数类型)。

为什么需要 std::function?

在 C++11 之前,如果我们想要实现一个接受"函数"作为参数的函数,通常会使用函数指针。但是函数指针有一些局限性:

  1. 不能存储带有状态的函数对象(如闭包)
  2. 不能直接存储成员函数
  3. 语法不够直观

std::function 解决了这些问题,提供了一个统一的接口来处理各种可调用对象。

std::function 的基本用法

存储普通函数

cpp
#include <iostream>
#include <functional>

// 普通函数
int add(int a, int b) {
return a + b;
}

int main() {
// 将普通函数存储到 std::function 对象中
std::function<int(int, int)> func = add;

// 调用函数
std::cout << "调用结果: " << func(5, 3) << std::endl;

return 0;
}

输出:

调用结果: 8

存储 Lambda 表达式

cpp
#include <iostream>
#include <functional>

int main() {
// 将 lambda 表达式存储到 std::function 对象中
std::function<int(int, int)> multiply = [](int a, int b) {
return a * b;
};

std::cout << "5 * 3 = " << multiply(5, 3) << std::endl;

return 0;
}

输出:

5 * 3 = 15

存储函数对象(Functor)

cpp
#include <iostream>
#include <functional>

// 自定义函数对象
struct Divider {
int operator()(int a, int b) const {
return a / b;
}
};

int main() {
// 将函数对象存储到 std::function 对象中
std::function<int(int, int)> divide = Divider();

std::cout << "10 / 2 = " << divide(10, 2) << std::endl;

return 0;
}

输出:

10 / 2 = 5

存储成员函数

cpp
#include <iostream>
#include <functional>

class Calculator {
public:
int add(int a, int b) {
return a + b;
}
};

int main() {
Calculator calc;

// 存储成员函数(需要使用 std::bind 或 lambda)
std::function<int(int, int)> adder = [&calc](int a, int b) {
return calc.add(a, b);
};

// 或者使用 std::bind
std::function<int(int, int)> adder2 = std::bind(&Calculator::add, &calc,
std::placeholders::_1,
std::placeholders::_2);

std::cout << "3 + 4 = " << adder(3, 4) << std::endl;
std::cout << "5 + 6 = " << adder2(5, 6) << std::endl;

return 0;
}

输出:

3 + 4 = 7
5 + 6 = 11

std::function 的高级特性

函数对象的传递与存储

std::function 可以作为函数参数或返回值,这使得函数式编程在 C++ 中变得更加容易。

cpp
#include <iostream>
#include <functional>
#include <vector>

// 函数接受一个操作函数作为参数
void processNumbers(const std::vector<int>& nums,
std::function<void(int)> processor) {
for (int num : nums) {
processor(num);
}
}

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

// 打印数字
processNumbers(numbers, [](int n) {
std::cout << n << " ";
});
std::cout << std::endl;

// 打印数字的平方
processNumbers(numbers, [](int n) {
std::cout << n * n << " ";
});
std::cout << std::endl;

return 0;
}

输出:

1 2 3 4 5 
1 4 9 16 25

默认构造与空函数检查

默认构造的 std::function 不包含任何可调用对象,称为空函数对象。我们可以通过 operator bool() 检查函数对象是否为空。

cpp
#include <iostream>
#include <functional>

void greet() {
std::cout << "Hello, World!" << std::endl;
}

int main() {
// 创建空函数对象
std::function<void()> empty;

// 创建非空函数对象
std::function<void()> func = greet;

// 检查函数对象是否为空
if (empty) {
std::cout << "empty 不是空函数对象" << std::endl;
} else {
std::cout << "empty 是空函数对象" << std::endl;
}

if (func) {
std::cout << "func 不是空函数对象" << std::endl;
func(); // 调用函数
}

return 0;
}

输出:

empty 是空函数对象
func 不是空函数对象
Hello, World!
注意

调用空的 std::function 对象会抛出 std::bad_function_call 异常!因此使用前应该先检查函数对象是否为空。

实际应用场景

回调函数

std::function 最常见的应用场景之一是实现回调机制,特别是在事件驱动的编程中。

cpp
#include <iostream>
#include <functional>
#include <string>

class Button {
private:
std::string m_name;
std::function<void()> m_onClick;

public:
Button(const std::string& name) : m_name(name), m_onClick(nullptr) {}

// 设置点击回调
void setOnClickHandler(std::function<void()> handler) {
m_onClick = handler;
}

// 模拟用户点击
void click() {
std::cout << "按钮 '" << m_name << "' 被点击!" << std::endl;
if (m_onClick) {
m_onClick();
}
}
};

int main() {
Button submitButton("提交");
Button cancelButton("取消");

// 设置点击回调
submitButton.setOnClickHandler([]() {
std::cout << "正在提交表单..." << std::endl;
});

cancelButton.setOnClickHandler([]() {
std::cout << "已取消操作!" << std::endl;
});

// 模拟用户点击
submitButton.click();
cancelButton.click();

return 0;
}

输出:

按钮 '提交' 被点击!
正在提交表单...
按钮 '取消' 被点击!
已取消操作!

策略模式

std::function 可以用来实现策略设计模式,使算法在运行时可以动态切换。

cpp
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>

// 使用 std::function 实现排序策略
class Sorter {
private:
std::function<bool(int, int)> m_compareStrategy;

public:
Sorter(std::function<bool(int, int)> strategy) : m_compareStrategy(strategy) {}

void sort(std::vector<int>& data) {
std::sort(data.begin(), data.end(), m_compareStrategy);
}

void setStrategy(std::function<bool(int, int)> strategy) {
m_compareStrategy = strategy;
}
};

// 打印向量的辅助函数
void printVector(const std::vector<int>& vec) {
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
}

int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
std::cout << "原始数组: ";
printVector(numbers);

// 创建一个使用升序策略的排序器
Sorter sorter([](int a, int b) { return a < b; });

// 升序排序
sorter.sort(numbers);
std::cout << "升序排序: ";
printVector(numbers);

// 切换到降序策略
sorter.setStrategy([](int a, int b) { return a > b; });

// 降序排序
sorter.sort(numbers);
std::cout << "降序排序: ";
printVector(numbers);

return 0;
}

输出:

原始数组: 5 2 8 1 9 3 
升序排序: 1 2 3 5 8 9
降序排序: 9 8 5 3 2 1

std::function 性能考虑

std::function 是一个强大的工具,但它确实会带来一些性能开销。与直接函数调用相比,std::function 可能会引入额外的间接调用和可能的堆分配。

提示

当性能至关重要时,可以考虑以下几点:

  1. 对于简单的场景,可以使用函数指针而不是 std::function
  2. 如果可能,使用模板而不是 std::function
  3. 注意 std::function 对象的复制开销

然而,对于大多数应用程序来说,std::function 带来的灵活性和易用性通常超过了性能开销的影响。

std::function vs 函数指针

下表比较了 std::function 和传统函数指针的区别:

特性std::function函数指针
语法清晰度
存储 Lambda支持不支持(C++23前)
存储函数对象支持不支持
存储成员函数支持(需绑定)支持特定语法
性能开销较高很低
类型擦除支持不支持

总结

std::function 是 C++11 引入的强大功能,为函数式编程提供了极大便利:

  1. 它可以存储、复制和调用多种可调用对象(普通函数、lambda、函数对象、成员函数)
  2. 它提供了类型安全的函数封装,使参数传递更加灵活
  3. 它在回调机制、策略模式等设计模式中有广泛应用
  4. 相比函数指针,它提供了更强的表达能力和更高的灵活性

然而,它也带来一些性能开销,在性能敏感的场景需要谨慎使用。

练习

  1. 使用 std::function 创建一个简单的计算器,支持加、减、乘、除四种操作。
  2. 实现一个简单的事件系统,允许注册和触发多个回调函数。
  3. 尝试使用 std::function 实现一个命令模式,允许用户执行和撤销操作。
  4. 比较直接调用函数与通过 std::function 调用函数的性能差异。

附加资源