成员指针(数据成员指针和成员函数指针)在库设计中常用于实现编译期类型安全、运行时动态访问以及泛型算法。下面通过几个实际案例展示其典型应用。
案例1:通用属性访问器(Property System)
目标:定义一个Property类,能够绑定到某个类的某个数据成员,并提供统一的get/set接口,支持批量操作。
#include<iostream>#include<vector>template<typenameClass,typenameType>classProperty{Type Class::*member;// 数据成员指针public:Property(Type Class::*m):member(m){}Typeget(constClass&obj)const{returnobj.*member;}voidset(Class&obj,constType&value)const{obj.*member=value;}};// 使用示例structPerson{std::string name;intage;};intmain(){Person p{"Alice",25};Property<Person,std::string>nameProp(&Person::name);Property<Person,int>ageProp(&Person::age);std::cout<<nameProp.get(p)<<" is "<<ageProp.get(p)<<std::endl;// Alice is 25ageProp.set(p,26);std::cout<<ageProp.get(p)<<std::endl;// 26// 批量操作:遍历多个属性std::vector<Property<Person,int>>intProps;intProps.emplace_back(&Person::age);// 可以添加更多 int 成员...for(auto&prop:intProps){prop.set(p,0);}std::cout<<ageProp.get(p)<<std::endl;// 0}设计价值:
- 将成员访问封装成对象,便于存储、传递和批量操作。
- 可用于实现图形界面的“属性编辑器”,运行时通过字符串名查找对应的
Property对象。
案例2:简易对象关系映射(ORM)
目标:定义一个通用Table模板,能够将 C++ 结构体映射到 SQL 表的行,使用成员指针读写字段,自动生成 INSERT/UPDATE 语句。
#include<iostream>#include<string>#include<vector>#include<sstream>// 字段描述template<typenameClass,typenameType>structField{std::string name;Type Class::*ptr;// 数据成员指针Field(conststd::string&n,Type Class::*p):name(n),ptr(p){}};// 表映射template<typenameClass>classTable{std::string tableName;std::vector<FieldBase*>fields;// 简化:实际用类型擦除或 variantpublic:Table(conststd::string&name):tableName(name){}template<typenameType>voidaddField(conststd::string&colName,Type Class::*ptr){fields.push_back(newField<Class,Type>(colName,ptr));}std::stringgenerateInsert(constClass&obj)const{std::stringstream ss;ss<<"INSERT INTO "<<tableName<<" (";for(size_t i=0;i<fields.size();++i){if(i)ss<<",";ss<<fields[i]->name;}ss<<") VALUES (";for(size_t i=0;i<fields.size();++i){if(i)ss<<",";// 通过成员指针获取值(需要类型转换,这里简化)// 实际实现需要根据类型生成正确字面量}ss<<")";returnss.str();}};// 使用示例(简化,只演示结构)structUser{intid;std::string name;};intmain(){Table<User>userTable("users");userTable.addField("id",&User::id);userTable.addField("name",&User::name);User u{1,"Alice"};// std::cout << userTable.generateInsert(u) << std::endl;// 输出:INSERT INTO users (id,name) VALUES (1,'Alice');}设计价值:
- 成员指针存储了字段在对象内的偏移量,运行时可以读写任意对象的该字段。
- ORM 库(如 ODB、sqlpp11)底层大量使用成员指针实现编译期反射。
案例3:信号槽(Signal-Slot)简化实现
目标:实现一个简单的信号槽机制,槽可以是任意类的成员函数(使用成员函数指针)。
#include<functional>#include<vector>#include<iostream>// 类型擦除的槽容器classSlotBase{public:virtual~SlotBase()=default;virtualvoidinvoke()=0;};template<typenameClass>classSlot:publicSlotBase{Class*obj;void(Class::*method)();// 成员函数指针public:Slot(Class*o,void(Class::*m)()):obj(o),method(m){}voidinvoke()override{(obj->*method)();}};classSignal{std::vector<SlotBase*>slots;public:template<typenameClass>voidconnect(Class*obj,void(Class::*method)()){slots.push_back(newSlot<Class>(obj,method));}voidemit(){for(auto*slot:slots)slot->invoke();}~Signal(){for(auto*s:slots)deletes;}};// 使用示例classLogger{public:voidinfo(){std::cout<<"Info logged\n";}voidwarn(){std::cout<<"Warning logged\n";}};intmain(){Signal sig;Logger logger;sig.connect(&logger,&Logger::info);sig.connect(&logger,&Logger::warn);sig.emit();// 输出两行}设计价值:
- 成员函数指针允许将任何类的成员函数注册为回调,无需继承自某个接口。
- 现代库(如
boost::signals2、Qt的 moc 本质上也是类似,但用元对象编译器扩展了语法)。
案例4:通用序列化 / 遍历成员
目标:实现一个能遍历对象所有指定类型成员的函数,用于序列化、哈希、打印等。
#include<iostream>#include<tuple>#include<string>// 辅助:遍历成员指针元组template<typenameClass,typename...Types>voidforEachMember(Class&obj,std::tuple<Types Class::*...>members,auto&&func){std::apply([&](auto...ptrs){(func(obj.*ptrs),...);},members);}// 定义结构体及其成员指针元组structPoint{intx,y;std::string label;};intmain(){Point p{10,20,"origin"};automembers=std::make_tuple(&Point::x,&Point::y,&Point::label);// 打印所有成员forEachMember(p,members,[](constauto&value){std::cout<<value<<" ";});// 输出:10 20 origin// 序列化为 JSON(简化)std::string json="{";forEachMember(p,members,[&](constauto&value){// 实际需处理不同类型,此处仅示意json+=std::to_string(value)+",";});json.back()='}';std::cout<<json<<std::endl;// {10,20,origin}}设计价值:
- 利用成员指针元组实现编译期反射,避免宏或代码生成器。
- 可用于实现通用的
operator<<、hash组合、对象比较等。
案例5:多态委托(Polymorphic Delegate)
目标:实现一个可以存储任意类成员函数(参数/返回值相同)的委托,并支持直接调用。
#include<memory>#include<iostream>template<typenameRet,typename...Args>classDelegate{structCallableBase{virtual~CallableBase()=default;virtualRetinvoke(Args...args)=0;};template<typenameClass>structCallable:CallableBase{Class*obj;Ret(Class::*method)(Args...);Callable(Class*o,Ret(Class::*m)(Args...)):obj(o),method(m){}Retinvoke(Args...args)override{return(obj->*method)(args...);}};std::unique_ptr<CallableBase>callable;public:template<typenameClass>Delegate(Class*obj,Ret(Class::*method)(Args...)):callable(std::make_unique<Callable<Class>>(obj,method)){}Retoperator()(Args...args)const{returncallable->invoke(args...);}};// 使用示例classCalculator{public:intadd(inta,intb){returna+b;}intsub(inta,intb){returna-b;}};intmain(){Calculator calc;Delegate<int,int,int>d(&calc,&Calculator::add);std::cout<<d(5,3)<<std::endl;// 8d=Delegate<int,int,int>(&calc,&Calculator::sub);std::cout<<d(5,3)<<std::endl;// 2}设计价值:
- 类似于
std::function但更轻量,且仅支持成员函数(或可扩展支持普通函数)。 - 用于事件系统、回调管理、命令模式等。
总结:成员指针在库设计中的优势
| 应用场景 | 使用的成员指针类型 | 核心价值 |
|---|---|---|
| 属性系统 | 数据成员指针 | 类型安全的成员访问封装,支持运行时动态访问 |
| ORM / 序列化 | 数据成员指针 | 存储偏移量,无需侵入式宏或代码生成,即可读写对象字段 |
| 信号槽 / 事件 | 成员函数指针 | 允许任意类的成员函数作为回调,解耦模块 |
| 泛型遍历 / 反射 | 数据成员指针元组 | 编译期遍历对象成员,实现通用算法(打印、哈希、比较) |
| 多态委托 | 成员函数指针 | 类型擦除 + 成员指针,实现类似std::function的轻量级委托 |
这些案例都体现了成员指针的核心理念:将“成员在类中的位置”作为一等公民,使得算法可以独立于具体对象,同时保持类型安全。在需要运行时操作对象成员且编译期绑定类型的场景下,成员指针是不可替代的工具。