C++ 中的可调用对象(Callable Objects)是指那些能够被调用执行的对象。这包括了函数、函数对象(也叫做仿函数,即重载了 operator() 的类或者结构体)、Lambda 表达式以及任何具有 operator() 的成员函数的对象。可调用对象在 C++ 标准库算法(如 std::for_each、std::transform 等)以及回调函数等场景中广泛使用。
任何普通的函数都可以作为可调用对象使用。
#include
#include
#include
// 普通函数
void print_number(int num) {
std::cout << num << std::endl;
}
int main()
{
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::for_each(numbers.begin(), numbers.end(), print_number);
return 0;
}
函数对象是通过重载operator()的类或者结构体实现的。
#include
#include
#include
// 函数对象(仿函数)
struct PrintNumber {
void operator()(int num) const {
std::cout << num << std::endl;
}
};
int main()
{
std::vector<int> numbers = {1, 2, 3, 4, 5};
PrintNumber print;
std::for_each(numbers.begin(), numbers.end(), print);
return 0;
}
C++11 引入了 Lambda 表达式,它可以很方便地定义匿名函数对象。
#include
#include
#include
int main()
{
std::vector<int> numbers = {1, 2, 3, 4, 5};
// Lambda 表达式
auto print_lambda = [](int num) {
std::cout << num << std::endl;
};
std::for_each(numbers.begin(), numbers.end(), print_lambda);
return 0;
}
在 C++ 中,可调用对象的类型通常不是直接可以使用的,但是可以使用 std::function 来包装它们,使得可调用对象的类型变得统一。
#include
#include
#include
#include
// 普通函数
void print_number(int num) {
std::cout << num << std::endl;
}
// 函数对象(仿函数)
struct PrintNumber {
void operator()(int num) const {
std::cout << num << std::endl;
}
};
// 使用 std::function 包装可调用对象
void use_callable(const std::function<void(int)>& callable, int num) {
callable(num);
}
int main()
{
// 使用 std::function 包装函数
use_callable(print_number, 12);
// 使用 std::function 包装函数对象
use_callable(PrintNumber(), 12);
// 使用 std::function 包装 Lambda 表达式
use_callable([](int num) { std::cout << num << std::endl; }, 12);
return 0;
}
使用可调用对象的注意事项如下:
std::function 是 C++11 标准库中的一个通用、多态的函数封装器。它允许将任何可调用对象(如函数、Lambda 表达式、函数对象或绑定表达式等)作为参数传递,或者赋值给变量。std::function 的灵活性使其成为实现回调、事件驱动编程以及更高级的编程技术的有力工具。
首先,需要包含
#include
#include // 包含 std::function
// 一个普通的函数
void print_hello() {
std::cout << "Hello, World!" << std::endl;
}
int main()
{
// 创建一个 std::function 对象,它可以调用没有参数的函数
std::function<void()> func = print_hello;
// 调用 func,这将调用 print_hello 函数
func(); // 输出: Hello, World!
return 0;
}
std::function 也可以用来封装 Lambda 表达式。
#include
#include
int main()
{
// 创建一个 std::function 对象,它可以调用接受一个 int 参数并返回 void 的函数或 Lambda
std::function<void(int)> func;
// 定义一个 Lambda 表达式,并将其赋值给 func
func = [](int x) {
std::cout << "Value: " << x << std::endl;
};
// 调用 func,传递一个整数参数
func(42); // 输出: Value: 42
return 0;
}
std::function 也可以用来包装类的成员函数,但这需要一些额外的处理,因为成员函数需要一个对象来调用。通常使用 std::bind 或者 Lambda 表达式来包装成员函数。
#include
#include
class MyClass {
public:
void print_value(int x) {
std::cout << "Value in MyClass: " << x << std::endl;
}
};
int main()
{
MyClass obj;
// 使用 Lambda 表达式包装成员函数
std::function<void(int)> func = [&obj](int x) {
obj.print_value(x);
};
// 调用 func,这将调用 MyClass 的成员函数
func(100); // 输出: Value in MyClass: 100
return 0;
}
std::function 的类型擦除是 std::function 能够封装任意可调用对象(如函数、Lambda 表达式、函数对象等)并统一调用的关键机制。类型擦除意味着在运行时,std::function 对象隐藏了它所包含的可调用对象的实际类型,使得可以以一种统一的方式处理它们。
(1)类型擦除的实现原理
std::function 内部通常使用小对象优化(Small Object Optimization, SOO)和类型擦除技术来存储和调用可调用对象。小对象优化是一种减少内存分配开销的技术,它允许 std::function 在内部直接存储较小的对象,避免动态内存分配。对于较大的对象,std::function 则会使用动态内存分配。
类型擦除则是通过虚函数和函数指针来实现的。std::function 内部维护了一个指向一个函数对象的指针,这个函数对象负责调用实际的可调用对象。这个函数对象通常是一个模板类,它根据封装的可调用对象的类型进行特化。当调用 std::function 对象时,它会通过这个函数对象的虚函数表找到正确的调用方式,然后间接调用实际的可调用对象。
(2)类型擦除的优缺点
优点:
缺点:
(3)使用注意事项
(4)示例代码
下面是一个简单的示例代码,展示了 std::function 的类型擦除特性:
#include
#include
void print_int(int x) {
std::cout << "Integer: " << x << std::endl;
}
void print_double(double x) {
std::cout << "Double: " << x << std::endl;
}
int main()
{
std::function<void(int)> func_int = print_int; // 封装一个接受 int 的函数
std::function<void(double)> func_double = print_double; // 封装一个接受 double 的函数
func_int(12); // 输出: Integer: 12
func_double(3.14); // 输出: Double: 3.14
// 尽管 func_int 和 func_double 的类型不同,但它们都是 std::function 对象,
// 这体现了类型擦除:可以以统一的方式处理它们。
return 0;
}
std::function 非常适合用作回调函数,允许传递任何满足特定签名的可调用对象。这在异步编程、事件处理以及需要灵活性的场景中特别有用。
#include
#include
#include // 用于线程
#include // 用于休眠
// 模拟一个异步操作,完成后调用回调函数
void async_operation(std::function<void()> callback) {
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::seconds(1));
// 异步操作完成后调用回调函数
if (callback) {
callback();
}
}
void on_operation_complete() {
std::cout << "Operation completed!" << std::endl;
}
int main()
{
// 使用普通函数作为回调函数
async_operation(on_operation_complete);
// 或者使用 Lambda 表达式
async_operation([]() {
std::cout << "Operation completed with lambda!" << std::endl;
});
// 等待足够的时间以确保异步操作完成
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
在这个例子中,async_operation 函数接受一个 std::function
std::function 可以与模板一起使用,以创建更通用的代码。
#include
#include
#include
#include
// 模板函数,接受一个 std::function 和一个容器
template <typename Container, typename Function>
void process_container(const Container& c, Function func) {
for (const auto& item : c) {
func(item);
}
}
void print_number(int n) {
std::cout << n << " ";
}
int main()
{
std::vector<int> numbers = { 1, 2, 3, 4, 5 };
// 使用 Lambda 表达式作为参数
process_container(numbers, [](int n) {
std::cout << n * n << " ";
});
std::cout << std::endl; // 输出: 1 4 9 16 25
// 使用普通函数作为参数
process_container(numbers, print_number);
std::cout << std::endl; // 输出: 1 2 3 4 5
return 0;
}
上面代码的输出为:
1 4 9 16 25
1 2 3 4 5
在这个例子中,process_container 是一个模板函数,它接受一个容器和一个 std::function。这使得可以对任何类型的容器和任何满足特定签名的可调用对象进行操作。
使用 std::function 的注意事项如下:
std::bind 是 C++ 标准库中的一个功能,它用于将可调用对象(如函数、函数对象或 Lambda 表达式)与特定的参数绑定在一起,从而生成一个新的可调用对象。这个新的可调用对象可以在稍后被调用,且其调用行为等同于原始可调用对象与绑定参数的结合。
std::bind 的基本语法如下:
auto new_callable = std::bind(callable, arg1, arg2, ...);
其中 callable 是要被绑定的可调用对象(函数、函数对象或 Lambda 表达式),arg1, arg2, … 是要传递给 callable 的参数。std::bind 返回一个新的可调用对象 new_callable,它保存了 callable 和其参数的信息。
std::bind 的一个强大特性是支持占位符 std::placeholders::_1, std::placeholders::_2, …,它们允许指定哪些参数在调用 new_callable 时应该被替换。这使得 std::bind 可以创建灵活的回调函数和延迟计算函数。
针对如下代码:
void print_int(int n) {
std::cout << n << " ";
}
std::function<void(int)> func_int1 = print_int;
std::function<void(int)> func_int2 = bind(print_int,std::placeholders::_1);
其中对于 std::function
这种方式的优点是简单、直接,并且通常比使用 std::bind 有更好的性能,因为 std::function 可以直接存储对 print_int 的引用或指针,而不需要额外的函数调用或对象来转发调用。
对于 std::function
#include
#include
void print_sum(int a, int b) {
std::cout << "Sum: " << a + b << std::endl;
}
int main()
{
// 绑定 print_sum 函数和参数 5, 10
auto bound_func = std::bind(print_sum, 5, 10);
// 调用 bound_func,输出 "Sum: 15"
bound_func();
return 0;
}
#include
#include
#include
#include
bool is_even(int n) {
return n % 2 == 0;
}
int main()
{
std::vector<int> numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 使用 std::bind 和占位符 _1 来创建一个新的可调用对象,该对象接受一个整数并检查它是否是偶数
auto is_even_binder = std::bind(is_even, std::placeholders::_1);
// 使用 std::remove_if 和绑定的可调用对象来移除所有偶数
numbers.erase(std::remove_if(numbers.begin(), numbers.end(), is_even_binder), numbers.end());
// 输出剩余的数字(奇数)
for (int num : numbers) {
std::cout << num << ' ';
}
std::cout << std::endl; // 输出: 1 3 5 7 9
return 0;
}
在这个例子中,std::bind(is_even, std::placeholders::_1) 创建了一个新的可调用对象 is_even_binder,它接受一个参数(由占位符 std::placeholders::_1 表示)并调用 is_even 函数。然后,将这个新的可调用对象传递给 std::remove_if,它遍历 numbers 容器并移除所有偶数。
#include
#include
#include
class Greeter {
public:
void greet(const std::string& name) const {
std::cout << "Hello, " << name << "!" << std::endl;
}
};
int main()
{
Greeter greeter;
// 绑定 greeter 对象的 greet 成员函数和占位符
auto bound_member_func = std::bind(&Greeter::greet, &greeter, std::placeholders::_1);
// 调用 bound_member_func,输出 "Hello, World!"
bound_member_func("World");
return 0;
}
这个例子创建了一个 Greeter 类的实例 greeter,并使用 std::bind 将 greet 成员函数和占位符 std::placeholders::_1 绑定到一起。这样,当调用 bound_member_func 时,它会调用 greeter 对象的 greet 成员函数并支持传递一个参数。
#include
#include
#include
#include
void print_info(const std::string& name, int age, const std::string& message) {
std::cout << "Name: " << name << ", Age: " << age << ", Message: " << message << std::endl;
}
int main()
{
std::string name = "Alice";
int fixed_age = 30;
// 使用 std::bind 绑定 name 和 fixed_age,保留第三个参数为占位符
auto bound_print_info = std::bind(print_info, name, fixed_age, std::placeholders::_1);
// 调用 bound_print_info,并传递额外的消息字符串
bound_print_info("Hello, world!"); // 输出: Name: Alice, Age: 30, Message: Hello, world!
return 0;
}
在这个例子中,std::bind 被用来绑定 name 和 fixed_age 参数到 print_info 函数,而第三个参数(消息字符串)则被保留为占位符 std::placeholders::_1。这样,当 bound_print_info 被调用时,就可以传递一个额外的字符串参数来替换占位符。
使用 std::bind 的注意事项如下: