您的位置:首页 > 健康 > 养生 > 软件开发公司名字_数字营销技术应用网站_百度招商客服电话_济南专业做网站

软件开发公司名字_数字营销技术应用网站_百度招商客服电话_济南专业做网站

2024/10/6 10:28:56 来源:https://blog.csdn.net/m0_73969414/article/details/142437099  浏览:    关键词:软件开发公司名字_数字营销技术应用网站_百度招商客服电话_济南专业做网站
软件开发公司名字_数字营销技术应用网站_百度招商客服电话_济南专业做网站

1. 三种可调用对象

在学习包装器之前,先回顾一下C++中三种用于定义可调用对象的方式:函数指针、仿函数(即函数对象)和 lambda 表达式。它们各有优缺点,适用于不同的场景。

a. 函数指针

  • 函数指针是指向函数的指针,可以通过它来调用函数。
  • 优点
    • 使用起来简单直接,适合指向独立的函数。
    • 支持将不同函数传递给相同的调用接口,非常灵活。
  • 缺点
    • 函数指针只能指向全局或静态函数,无法指向类的非静态成员函数。
    • 类型定义复杂,且没有类型检查的优势,容易导致意外调用错误函数。
    • 不支持状态维护,无法保存上下文信息。

b. 仿函数(函数对象)

  • 仿函数是通过重载 operator() 运算符的类或结构体,允许对象像函数一样调用。
  • 优点
    • 可以在类中保存状态(例如类的成员变量),支持复杂的逻辑。
    • 类型安全,编译时检查更严格。
    • 可以通过模板进行泛型编程,使用灵活。
  • 缺点
    • 实现上比函数指针复杂,需要定义一个类和重载 () 操作符,不适合统一类型。
    • 因为仿函数对象可以存储状态,内存消耗可能更大。

c. Lambda 表达式

  • Lambda 表达式是 C++11 引入的一种匿名函数,支持通过捕获列表将上下文变量引入到表达式中。
  • 优点
    • 简洁:可以在函数内部定义临时的可调用对象,无需显式声明函数或类。
    • 灵活:支持通过捕获列表捕获外部变量,可以是按值或按引用捕获。
    • 支持泛型:可以与 auto、模板结合使用。
    • 更好的内联优化:因为 lambda 是内联的,编译器可以对其进行更多的优化。
  • 缺点
    • 不适合过于复杂的逻辑,因为会让代码难以阅读和维护。
    • C++11 引入,老版本的编译器不支持。

为什么引入包装器?

  • 概念std::function 是一个通用的函数包装器,可以存储任何可以调用的目标,包括函数指针、仿函数和 lambda 表达式。
  • 引入的原因
    • 统一接口:函数指针、仿函数和 lambda 各有不同的语法和特点,std::function 允许我们使用统一的方式处理这些不同的可调用对象。它让代码更加灵活,并简化了接口设计。
    • 类型安全std::function 提供类型安全的调用机制,避免了函数指针的潜在问题。
    • 支持存储状态:与函数指针不同,std::function 能够存储可调用对象的状态,例如捕获的 lambda 变量或仿函数对象的状态。

总结:

  • 如果你需要简单地调用一个全局函数,函数指针足够了。
  • 如果你需要一个保存状态且可调用的对象,选择仿函数对象
  • 当需要临时的、简洁的可调用对象时,lambda 表达式是首选。
  • 如果你希望统一处理各种可调用对象,std::function 这种包装器能提供极大的便利。

2. function基础使用

C++中的function就是一种函数包装器,其本质也是一个类模板:

template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
  • Ret是可调用对象返回值的类型
  • Args是可调用对象的参数类型

下面是一个示例代码,展示如何将不同的可调用对象(函数指针、仿函数、lambda 表达式)传递给相同的调用接口,并展示 std::function 的灵活性。

#include <iostream>
#include <functional>// 定义函数:接受整数并输出其平方
void square(int x) {std::cout << "Square: " << x * x << std::endl;
}// 定义仿函数:计算整数的立方
struct Cube {void operator()(int x) const {std::cout << "Cube: " << x * x * x << std::endl;}
};// 统一的调用接口,接受std::function类型的可调用对象
void call_with_10(const std::function<void(int)>& func) {func(10); // 将10传递给可调用对象
}int main() {// 1. 使用函数指针std::function<void(int)> func_ptr = square;call_with_10(func_ptr);  // 输出 Square: 100// 2. 使用仿函数对象std::function<void(int)> functor = Cube();call_with_10(functor);   // 输出 Cube: 1000// 3. 使用lambda表达式std::function<void(int)> lambda = [](int x) {std::cout << "Lambda: " << x + 5 << std::endl;};call_with_10(lambda);    // 输出 Lambda: 15return 0;
}

不仅仅是可以调用以上的可调用对象,类的静态和非静态成员函数都可以,以下为具体示例:

示例代码

#include <iostream>
#include <functional>// 定义一个简单的类
class MyClass {
public:static int staticVar;int nonStaticVar;static void staticFunction(int x) {std::cout << "Static Function: staticVar = " << staticVar << ", x = " << x << std::endl;}void nonStaticFunction(int x) {std::cout << "Non-static Function: nonStaticVar = " << nonStaticVar << ", x = " << x << std::endl;}
};// 初始化静态成员变量
int MyClass::staticVar = 42;int main() {// 1. 调用静态成员函数MyClass::staticFunction(10);  // 直接通过类名调用,不需要实例化对象// 2. 创建对象,调用非静态成员函数MyClass obj;obj.nonStaticVar = 5;obj.nonStaticFunction(10);  // 通过对象调用非静态成员函数// 3. 使用 std::function 绑定静态成员函数std::function<void(int)> staticFuncPtr = &MyClass::staticFunction;staticFuncPtr(20);  // 可以通过 std::function 调用静态成员函数// 4. 使用 std::function 绑定非静态成员函数std::function<void(MyClass&, int)> nonStaticFuncPtr = &MyClass::nonStaticFunction;nonStaticFuncPtr(obj, 30);  // 必须传递对象实例作为第一个参数return 0;
}

代码分析:

使用 std::function 绑定成员函数

  • 静态成员函数:因为静态成员函数不依赖于对象,可以直接通过 std::function 绑定并调用,类似于普通函数指针。
    std::function<void(int)> staticFuncPtr = MyClass::staticFunction;
    staticFuncPtr(20);
    
  • 非静态成员函数:非静态成员函数必须绑定到一个对象实例上,才能被调用。因此,std::function 的第一个参数必须传递对象实例。
    std::function<void(MyClass&, int)> nonStaticFuncPtr = &MyClass::nonStaticFunction;
    nonStaticFuncPtr(obj, 30);
    

总结:

  • 静态成员函数与类本身关联,不依赖于对象,可以直接通过类名调用。
  • 非静态成员函数与对象关联,必须通过对象实例调用。
  • 通过 std::function 可以统一管理和调用这些函数,但非静态成员函数需要额外传递对象实例作为参数。取静态成员函数的地址可以不用取地址运算符&,但取非静态成员函数的地址必须使用取地址运算符&,一般都可以写上。

3. 简化代码

为了更好地理解包装器的作用,下面我们通过一个具体的例子来展示如何使用包装器(例如 std::function)来简化代码,尤其是在处理不同类型的可调用对象(函数指针、仿函数、lambda 表达式)时的简化效果。

场景说明

假设我们要实现一个事件处理系统,允许用户注册不同的回调函数来处理某个事件。这些回调函数可以是普通的函数、类的成员函数(包括静态和非静态),也可以是 lambda 表达式或仿函数。为了统一管理这些不同的可调用对象,我们可以使用 std::function 作为包装器。

前后的代码对比

a. 没有包装器的代码(手动区分不同的可调用对象)
#include <iostream>// 处理函数指针
void processFunctionPointer(void (*callback)(int)) {callback(42); // 执行函数指针
}// 处理类的静态成员函数
void processStaticMember(void (*callback)(int)) {callback(42); // 执行静态成员函数
}// 处理类的非静态成员函数
void processNonStaticMember(void (MyClass::*callback)(int), MyClass& obj) {(obj.*callback)(42); // 执行非静态成员函数
}class MyClass {
public:void nonStaticFunction(int x) {std::cout << "Non-static function: " << x << std::endl;}static void staticFunction(int x) {std::cout << "Static function: " << x << std::endl;}
};// 普通函数
void normalFunction(int x) {std::cout << "Normal function: " << x << std::endl;
}int main() {MyClass obj;processFunctionPointer(normalFunction);processStaticMember(MyClass::staticFunction);processNonStaticMember(&MyClass::nonStaticFunction, obj);return 0;
}
  • 需要为每种类型的可调用对象(函数指针、静态成员函数、非静态成员函数)编写单独的处理函数,增加了代码复杂度。
b. 使用 function 的代码(简化代码)
#include <iostream>
#include <functional>  // 引入 std::functionclass MyClass {
public:void nonStaticFunction(int x) {std::cout << "Non-static function: " << x << std::endl;}static void staticFunction(int x) {std::cout << "Static function: " << x << std::endl;}
};// 普通函数
void normalFunction(int x) {std::cout << "Normal function: " << x << std::endl;
}// 通用处理函数,使用std::function简化代码,统一调用接口
void processEvent(const std::function<void(int)>& callback) {callback(42); 
}int main() {MyClass obj;processEvent(normalFunction);processEvent(MyClass::staticFunction);processEvent(std::bind(&MyClass::nonStaticFunction, &obj, std::placeholders::_1));// bind这块后面会解释processEvent([](int x) { std::cout << "Lambda: " << x << std::endl; });return 0;
}

通过使用 std::function 这样的包装器,我们可以将不同类型的可调用对象统一管理,并且简化了回调函数的处理逻辑,使代码更简洁、灵活和可扩展。这也充分体现了包装器在实际开发中的作用。

4. bind的基本使用

std::bind 是 C++ 标准库中的一个函数模板,它允许你将函数的一些参数提前绑定,生成一个新的可调用对象。std::bind 可以绑定普通函数、成员函数、以及其他可调用对象,常用于生成部分应用函数或将成员函数绑定到对象实例上。可见,bind的本质就是一个被封装过的一个类模板,会根据传入的函数和参数列表自动生成。
在这里插入图片描述

语法

auto boundFunction = std::bind(callable, arg1, arg2, ..., std::placeholders::_1, ...);
  • callable: 可调用对象,如普通函数、成员函数、仿函数或 lambda。
  • arg1, arg2,…: 需要提前绑定的参数。
  • std::placeholders::_1, std::placeholders::_2,…: 表示参数占位符,表示调用时传递的参数会被放置在对应的位置。

主要用途

  1. 绑定普通函数的一些参数,从而达到调整参数顺序或个数的效果。
  2. 绑定类的非静态成员函数到具体对象上,从而创建一个新的可调用对象。
  3. 生成可部分应用的函数

示例 1:绑定普通函数

#include <iostream>
#include <functional>void printSum(int a, int b) {std::cout << "Sum: " << a + b << std::endl;
}int main() {// 绑定第一个参数为 10auto boundFunc = std::bind(printSum, 10, std::placeholders::_1);boundFunc(5); // 输出 Sum: 15return 0;
}

分析

  • std::bind(printSum, 10, std::placeholders::_1) 创建了一个新函数 boundFunc,其中 printSum 的第一个参数被固定为 10,第二个参数由调用时提供。
  • boundFunc(5) 被调用时,相当于 printSum(10, 5)

示例 2:绑定非静态成员函数

#include <iostream>
#include <functional>class MyClass {
public:void display(int x) {std::cout << "Value: " << x << std::endl;}
};int main() {MyClass obj;// 绑定对象 obj 到成员函数 displayauto boundFunc = std::bind(&MyClass::display, &obj, std::placeholders::_1);boundFunc(100); // 输出 Value: 100return 0;
}

分析

  • std::bind(&MyClass::display, &obj, std::placeholders::_1) 将成员函数 display 绑定到对象 obj,生成了一个可以直接调用的函数 boundFunc
  • 调用 boundFunc(100) 时,相当于执行 obj.display(100)

示例 3:绑定多个参数

#include <iostream>
#include <functional>void multiply(int a, int b, int c) {std::cout << "Result: " << a * b * c << std::endl;
}int main() {// 绑定 a = 2, b = 3,c 由调用时提供auto boundFunc = std::bind(multiply, 2, 3, std::placeholders::_1);boundFunc(4);  // 输出 Result: 24 (2 * 3 * 4)return 0;
}

std::bind(multiply, 2, 3, std::placeholders::_1) 绑定了 multiply 函数的前两个参数 23,剩余的参数 c 由调用时提供。

总结

  • std::bind 是一个强大的工具,用来将函数的某些参数固定或绑定。
  • 它常用于延迟调用、生成部分应用函数以及将非静态成员函数与对象绑定。
  • 结合 占位符,我们可以灵活地控制传递参数的位置与数量,使代码更加简洁和灵活。

包装器在实践当中使用很多,功能非常强大,但也可能让代码变得复杂,所以在使用时需要小心。如果学会了就会对代码的理解有进了一步,恭喜你~ 如果文章对你有帮助的话不妨点个赞。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com