c++(6)function与成员函数
C++(6)之function与成员函数
Author: Once Day Date: 2025年5月18日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…
漫漫长路,有人对你微笑过嘛…
全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客
参考文章:
1. 概述
std::function是C++11引入的一个通用多态函数封装器,是一个类模板。它可以封装各种可调用对象(Callable Objects),包括普通函数、函数指针、lamda表达式、仿函数以及成员函数等,使它们形成一个统一的函数调用接口。从而使得算法能够不依赖具体的函数类型,只要是满足特定调用形式的可调用对象即可。这大大提高了代码的灵活性和泛用性。
std::function的基本使用方法如下:
- 声明一个std::function对象,并指定其参数类型和返回值类型,形如
std::function<返回值类型(参数类型列表)> 变量名。 - 将各种可调用对象绑定到该std::function对象上。
- 像普通函数一样使用std::function对象进行调用。
下面是一些具体的代码示例:
}#include
using namespace std;
// 普通函数
int foo(int a, int b) { return a + b; }
// lambda表达式
auto lambda = [](int a, int b) { return a + b; };
// 函数对象类
struct Functor {
int operator()(int a, int b) { return a + b; }
};
// 类成员函数
struct Bar {
int add(int a, int b) { return a + b; }
};
int main() {
// 封装普通函数
function<int(int, int)> func1 = foo;
// 封装lambda表达式
function<int(int, int)> func2 = lambda;
// 封装函数对象
function<int(int, int)> func3 = Functor();
// 封装类成员函数
Bar bar;
function<int(int, int)> func4 = bind(&Bar::add, &bar, placeholders::_1, placeholders::_2);
// 调用
int result1 = func1(1, 2); // 1+2=3
int result2 = func2(3, 4); // 3+4=7
int result3 = func3(5, 6); // 5+6=11
int result4 = func4(7, 8); // 7+8=15
return 0;
}
可以看到,使用std::function后,所有不同类型的可调用对象都可以用统一的方式进行调用,非常方便。std::function对象与bind配合使用,可以很方便地将类成员函数转换成与普通函数一样的调用形式。
下面介绍一下std::function与其他几种函数对象的区别和联系:
- 函数指针:函数指针可以指向一个普通函数,但无法直接保存lambda、仿函数等其他可调用对象。而std::function可以统一封装各类可调用对象。函数指针可以隐式转换为对应的std::function对象。
- lambda表达式:lambda表达式是C++11新引入的匿名函数写法,可以方便地在需要时定义一个内联的函数对象。但lambda表达式的类型无法直接声明,必须借助std::function或者auto关键字。lambda表达式可以隐式转换为对应的std::function对象。
- 仿函数:仿函数(Functor)是通过在类中重载()运算符模拟函数调用的类对象。一般在声明时就需要确定其参数和返回值类型。因此仿函数类型无法直接用于模板参数传递,而std::function对象可以。可以将仿函数对象赋给对应的std::function对象。
- 成员函数指针:成员函数指针可以指向类的非静态成员函数,使用时需要提供一个该类的对象。无法直接用于一般化的函数回调。而std::function搭配bind/mem_fn可以将成员函数转换为与普通函数相同的调用形式,一般化为普通的函数回调,用起来更加灵活方便。
std::function是对各种可调用对象的一个统一封装,使它们拥有相同的函数调用形式。std::function对象可以像普通函数一样作为算法的模板参数传递,大大提高了代码的复用性和灵活性。其缺点是会带来少量的运行时开销。
2. function实现原理
std::function实际上是一个类模板,其内部维护了一个能够容纳各种可调用对象的内存缓冲区,以及一系列指向类型擦除接口的函数指针。不同类型的可调用对象在绑定到std::function时,会被复制到这个内存缓冲区中,并动态生成一个能够调用该对象的类型擦除接口,将接口的函数指针保存起来。这个过程叫做类型擦除(Type Erasure)。
所谓类型擦除,指的是在编译期将一组具有相同接口的类型抽象为一个统一的类型,从而实现运行期的多态调用。std::function就是通过类型擦除技术,将不同类型的可调用对象抽象为统一的调用接口,实现了对不同函数对象的多态调用。
假设我们先后将一个普通函数指针fp和一个lambda表达式lam绑定到同一个std::function对象func中:
int foo(int a, int b) { return a+b; }
auto lam = [](int a, int b) { return a+b; };
std::function<int(int,int)> func;
func = foo; // 绑定函数指针
func(1, 2); // 调用函数指针
func = lam; // 绑定lambda
func(3, 4); // 调用lambda
对func的第一次赋值,会将函数指针foo复制到func的内存缓冲区中,并动态生成一个调用该函数指针的类型擦除接口,大致如下:
// func内部生成的类型擦除接口 - 调用函数指针
int invoke(void* fp, int a, int b) {
return (*(int(*)(int,int))fp)(a, b);
}
对func的第二次赋值,会将lambda表达式lam复制到func的内存缓冲区中(覆盖之前的foo),并动态生成一个调用该lambda的新的类型擦除接口,大致如下:
// func内部生成的类型擦除接口 - 调用lambda
int invoke(void* lam, int a, int b) {
return (*(lambda类型*)lam)(a, b);
}
后续对func的调用,就会通过函数指针invoke,跳转到绑定的函数对象那里去执行。
std::function通过在内部维护一个原始内存缓冲区来存储绑定的可调用对象,再结合类型擦除技术动态生成调用被绑定对象的统一接口,从而实现了对不同类型可调用对象的封装与多态调用。这种实现带来了一定的空间和时间开销,但提供了非常灵活方便的泛型编程手段,是一种空间换时间的trade-off.
std::function的实际实现要比上面描述的复杂得多,涉及到SBO(small buffer optimization)、移动语义、引用计数等诸多复杂的编程技术,但基本原理就是类型擦除+多态调用。
3. 成员函数转换
std::function一种典型使用是与类成员函数结合,在基类和派生类之间实现成员函数指针的调用接口自动转换(类指针偏移地址自动修正)。下面是一段示例代码:
#include <stdint.h>
#include <cxxabi.h>
#include <functional>
#include <string>
#include <unordered_map>
// 获取类型信息
template < typename T >
static const char *GetTypeName()
{
// 获取类型信息
const char *type_name = typeid(T).name();
// 使用CXXABI获取函数指针
const char *name = abi::__cxa_demangle(type_name, NULL, NULL, NULL);
return name;
}
// 打印成员变量信息
template < typename MemberType >
static void PrintMemberVariableInfo(MemberType &member, const char *name)
{
uint64_t *data = reinterpret_cast< uint64_t * >(&member);
printf(" MemberVar : name %-28s, address %p, value 0x%lx, type %s\n", name, data, data[0],
GetTypeName< MemberType >());
}
// 打印成员函数对象信息
template < typename MemberFuncType >
static void PrintMemberFunctionInfo(MemberFuncType &func, const char *name)
{
const uint64_t *data = reinterpret_cast< const uint64_t * >(&func);
printf(" MemberFunc: name %-28s, object %p, func 0x%lx, (this*) offset 0x%lx, type %s\n",
name, data, data[0], data[1], GetTypeName< MemberFuncType >());
}
// 打印成员函数对象信息
template < typename MemberFuncType >
static void PrintMemberFunctionInfo2(MemberFuncType &func, const char *name)
{
const uint64_t *data = reinterpret_cast< const uint64_t * >(&func);
printf(" MemberFunc: name %-28s, object %p, func 0x%lx, (this*) offset 0x%lx, type %s\n",
name, data, data[0], data[1], GetTypeName< MemberFuncType >());
}
// 打印 std::function 对象信息
template < typename FuncType >
static void PrintFunctionObjectInfo(std::function< FuncType > &func, const char *name)
{
// 打印参数信息
const uint64_t *data = reinterpret_cast< const uint64_t * >(&func);
printf(" FuncObject: name %-28s, object %p, func 0x%lx, (this*) offset 0x%lx, type %s\n",
name, data, data[0], data[1], GetTypeName< FuncType >());
}
// 使用给定参数调用函数对象
template < typename FuncType, typename DataType >
static int32_t CallFunctionObject(FuncType &func, DataType &data)
{
// 打印参数信息
printf(
" call converter with data: address %p, type: %s\n", &data, GetTypeName< DataType >());
// 调用函数对象
return func(data);
}
class IfaceConfig {
public:
void SetIfaceData(uint64_t data)
{
iface_data_ = data;
printf(" IfaceConfig - Set iface data: %lu, address %p == %p [%s]\n", iface_data_,
&iface_data_, iface_data_address,
&iface_data_ == iface_data_address ? "true" : "false");
}
/* private: */
uint64_t iface_data_;
static uint64_t *iface_data_address;
};
uint64_t *IfaceConfig::iface_data_address = nullptr;
class EtherConfig {
public:
void SetEtherData(uint64_t data)
{
ether_data_ = data;
printf(" EtherConfig - Set ether data: %lu, address %p == %p [%s]\n", ether_data_,
ðer_data_, ether_data_address,
ðer_data_ == ether_data_address ? "true" : "false");
}
/* private: */
uint64_t ether_data_;
static uint64_t *ether_data_address;
};
uint64_t *EtherConfig::ether_data_address = nullptr;
class PhysicalIfaceConfig : public IfaceConfig, public EtherConfig {
public:
void SetPhysicalData(uint64_t data)
{
physical_data_ = data;
printf(" PhysicalConfig - Set physical data: %lu, address %p == %p [%s]\n",
physical_data_, &physical_data_, physical_data_address,
&physical_data_ == physical_data_address ? "true" : "false");
}
/* private: */
uint64_t physical_data_;
static uint64_t *physical_data_address;
};
uint64_t *PhysicalIfaceConfig::physical_data_address = nullptr;
// 定义一个函数指针类型, 用于转换节点数据
template < typename DataType >
using NodeConverter = std::function< int32_t(DataType &data) >;
// 物理接口配置节点数据转换器映射表
using PhysicalNodeConverter = NodeConverter< PhysicalIfaceConfig >;
using PhysicalNodeConverterMap = std::unordered_map< std::string, PhysicalNodeConverter >;
// 模版函数 - 获取节点的值并赋值保存
template < typename DataType, typename FieldType >
static NodeConverter< DataType > MakeNodeConverter(void (DataType::*set_value)(FieldType))
{
return [set_value](DataType &data) -> int32_t {
FieldType temp = 8848;
// 打印 data 地址
printf(" NodeConverter - data: %p => %s\n", &data, GetTypeName< DataType >());
// 打印参数信息
PrintMemberFunctionInfo2(set_value, "NodeConverter::SetFunc");
// 直接赋值 - 通过 SetFunc 函数进行赋值
(data.*set_value)(temp);
return 0;
};
}
int main(void)
{
PhysicalIfaceConfig physical = {};
IfaceConfig &Iface = physical;
EtherConfig ðer = physical;
// 初始化地址信息
IfaceConfig::iface_data_address = &physical.iface_data_;
EtherConfig::ether_data_address = &physical.ether_data_;
PhysicalIfaceConfig::physical_data_address = &physical.physical_data_;
// EtherConfig::SetEtherData 测试
printf("\nEtherConfig::SetEtherData\n");
printf(" Member function info:\n");
auto set_ether_data_auto = &PhysicalIfaceConfig::SetEtherData;
void (PhysicalIfaceConfig::*set_ether_data)(uint64_t) = &PhysicalIfaceConfig::SetEtherData;
PrintMemberFunctionInfo(set_ether_data_auto, "set_ether_data_auto");
PrintMemberFunctionInfo(set_ether_data, "set_test_data2");
printf(" Function object info:\n");
auto ether_converter_auto = MakeNodeConverter(set_ether_data_auto);
PrintFunctionObjectInfo(ether_converter_auto, "ether_converter_auto");
auto ether_converter = MakeNodeConverter(set_ether_data);
PrintFunctionObjectInfo(ether_converter, "ether_converter");
// 生成映射表
printf("\nGenerate physical_converter_map:\n");
PhysicalNodeConverterMap physical_converter_map;
physical_converter_map["ether_converter_auto"] = ether_converter_auto;
physical_converter_map["ether_converter"] = ether_converter;
auto iter5 = physical_converter_map.find("ether_converter_auto");
auto func5 = iter5->second;
PrintFunctionObjectInfo(func5, "PhysicalMap::ether_converter_auto");
CallFunctionObject(func5, physical);
printf("\n");
auto iter6 = physical_converter_map.find("ether_converter");
auto func6 = iter6->second;
PrintFunctionObjectInfo(func6, "PhysicalMap::ether_converter");
CallFunctionObject(func6, physical);
return 0;
}
下面是这段代码的关键部分解析:
- 类的继承关系:PhysicalIfaceConfig继承自IfaceConfig和EtherConfig,形成了多重继承的关系。在main函数中,创建了一个PhysicalIfaceConfig对象physical,并用其对IfaceConfig和EtherConfig部分进行了切片。
- 成员函数指针的使用:代码中定义了两个成员函数指针set_ether_data_auto和set_ether_data,分别使用auto自动推导和显式声明的方式,指向了PhysicalIfaceConfig::SetEtherData函数。使用PrintMemberFunctionInfo函数打印成员函数指针的信息,可以看到它们都指向同一个函数,只是类型不同而已。
- std::function对象的绑定:使用MakeNodeConverter函数模板,将成员函数指针绑定到std::function对象ether_converter_auto和ether_converter上。其中,MakeNodeConverter的模板参数DataType为PhysicalIfaceConfig,FieldType为uint64_t,返回值类型为NodeConverter。std::function对象的类型为:std::function<int32_t(PhysicalIfaceConfig&)>。
- 类型擦除与多态调用:将ether_converter_auto和ether_converter插入到PhysicalNodeConverterMap中,得到一个统一的std::function类型,实现了类型擦除。在后续的查找和调用中,通过map的key值获取对应的std::function对象,再通过统一的调用方式CallFunctionObject来执行,实现了多态调用。
- 打印调试信息:代码中大量使用了打印函数,如PrintMemberFunctionInfo、PrintFunctionObjectInfo等,来打印成员函数指针、std::function对象的内部信息,如函数地址、this指针偏移等,可以帮助理解和调试。此外还打印了传递给std::function的physical对象的信息,可以看到std::function确实捕获了physical对象,并在调用时将其作为this指针传递给了成员函数。
- 指针的特殊用法:代码中使用了两个static成员变量iface_data_address和ether_data_address来记录physical对象中,基类子对象的成员变量地址。目的是为了验证在多态调用时,传递给std::function的physical对象,其中基类子对象的地址与直接访问physical时是一致的,印证了多态调用的正确性。
这段代码运行结果如下:

从输出信息来看,基类对象的指针偏移记录在成员函数指针对象上,所以PhysicalIfaceConfig::SetEtherData会有一个0x8的偏移量,但EtherConfig::SetEtherData没有偏移量。
把EtherConfig::SetEtherData赋值给ether_converter后,会自动生成一个新的function函数对象,并且自动把PhysicalConfig类指针偏移量加上8个字节,然后再执行目标函数,相当于自动完成PhysicalConfig到EtherConfig的向上转换。
PhysicalIfaceConfig::SetEtherData则不一样,function对象传递过程没有发生任何变化,在成员函数执行时,自动添加8个字节的偏移量。
更多推荐


所有评论(0)