成员指针(数据成员指针和成员函数指针)在库设计中常用于实现 编译期类型安全 、运行时动态访问 以及 泛型算法。下面通过几个实际案例展示其典型应用。
案例1:通用属性访问器(Property System)
目标:定义一个 Property 类,能够绑定到某个类的某个数据成员,并提供统一的 get/set 接口,支持批量操作。
cpp
#include <iostream>
#include <vector>
template<typename Class, typename Type>
class Property {
Type Class::* member; // 数据成员指针
public:
Property(Type Class::* m) : member(m) {}
Type get(const Class& obj) const {
return obj.*member;
}
void set(Class& obj, const Type& value) const {
obj.*member = value;
}
};
// 使用示例
struct Person {
std::string name;
int age;
};
int main() {
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 25
ageProp.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 语句。
cpp
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
// 字段描述
template<typename Class, typename Type>
struct Field {
std::string name;
Type Class::* ptr; // 数据成员指针
Field(const std::string& n, Type Class::* p) : name(n), ptr(p) {}
};
// 表映射
template<typename Class>
class Table {
std::string tableName;
std::vector<FieldBase*> fields; // 简化:实际用类型擦除或 variant
public:
Table(const std::string& name) : tableName(name) {}
template<typename Type>
void addField(const std::string& colName, Type Class::* ptr) {
fields.push_back(new Field<Class, Type>(colName, ptr));
}
std::string generateInsert(const Class& 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 << ")";
return ss.str();
}
};
// 使用示例(简化,只演示结构)
struct User {
int id;
std::string name;
};
int main() {
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)简化实现
目标:实现一个简单的信号槽机制,槽可以是任意类的成员函数(使用成员函数指针)。
cpp
#include <functional>
#include <vector>
#include <iostream>
// 类型擦除的槽容器
class SlotBase {
public:
virtual ~SlotBase() = default;
virtual void invoke() = 0;
};
template<typename Class>
class Slot : public SlotBase {
Class* obj;
void (Class::* method)(); // 成员函数指针
public:
Slot(Class* o, void (Class::* m)()) : obj(o), method(m) {}
void invoke() override {
(obj->*method)();
}
};
class Signal {
std::vector<SlotBase*> slots;
public:
template<typename Class>
void connect(Class* obj, void (Class::* method)()) {
slots.push_back(new Slot<Class>(obj, method));
}
void emit() {
for (auto* slot : slots) slot->invoke();
}
~Signal() { for (auto* s : slots) delete s; }
};
// 使用示例
class Logger {
public:
void info() { std::cout << "Info logged\n"; }
void warn() { std::cout << "Warning logged\n"; }
};
int main() {
Signal sig;
Logger logger;
sig.connect(&logger, &Logger::info);
sig.connect(&logger, &Logger::warn);
sig.emit(); // 输出两行
}
设计价值:
- 成员函数指针允许将任何类的成员函数注册为回调,无需继承自某个接口。
- 现代库(如
boost::signals2、Qt的 moc 本质上也是类似,但用元对象编译器扩展了语法)。
案例4:通用序列化 / 遍历成员
目标:实现一个能遍历对象所有指定类型成员的函数,用于序列化、哈希、打印等。
cpp
#include <iostream>
#include <tuple>
#include <string>
// 辅助:遍历成员指针元组
template<typename Class, typename... Types>
void forEachMember(Class& obj, std::tuple<Types Class::*...> members, auto&& func) {
std::apply([&](auto... ptrs) {
(func(obj.*ptrs), ...);
}, members);
}
// 定义结构体及其成员指针元组
struct Point {
int x, y;
std::string label;
};
int main() {
Point p{10, 20, "origin"};
auto members = std::make_tuple(&Point::x, &Point::y, &Point::label);
// 打印所有成员
forEachMember(p, members, [](const auto& value) {
std::cout << value << " ";
});
// 输出:10 20 origin
// 序列化为 JSON(简化)
std::string json = "{";
forEachMember(p, members, [&](const auto& value) {
// 实际需处理不同类型,此处仅示意
json += std::to_string(value) + ",";
});
json.back() = '}';
std::cout << json << std::endl; // {10,20,origin}
}
设计价值:
- 利用成员指针元组实现编译期反射,避免宏或代码生成器。
- 可用于实现通用的
operator<<、hash组合、对象比较等。
案例5:多态委托(Polymorphic Delegate)
目标:实现一个可以存储任意类成员函数(参数/返回值相同)的委托,并支持直接调用。
cpp
#include <memory>
#include <iostream>
template<typename Ret, typename... Args>
class Delegate {
struct CallableBase {
virtual ~CallableBase() = default;
virtual Ret invoke(Args... args) = 0;
};
template<typename Class>
struct Callable : CallableBase {
Class* obj;
Ret (Class::* method)(Args...);
Callable(Class* o, Ret (Class::* m)(Args...)) : obj(o), method(m) {}
Ret invoke(Args... args) override {
return (obj->*method)(args...);
}
};
std::unique_ptr<CallableBase> callable;
public:
template<typename Class>
Delegate(Class* obj, Ret (Class::* method)(Args...))
: callable(std::make_unique<Callable<Class>>(obj, method)) {}
Ret operator()(Args... args) const {
return callable->invoke(args...);
}
};
// 使用示例
class Calculator {
public:
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
};
int main() {
Calculator calc;
Delegate<int, int, int> d(&calc, &Calculator::add);
std::cout << d(5, 3) << std::endl; // 8
d = Delegate<int, int, int>(&calc, &Calculator::sub);
std::cout << d(5, 3) << std::endl; // 2
}
设计价值:
- 类似于
std::function但更轻量,且仅支持成员函数(或可扩展支持普通函数)。 - 用于事件系统、回调管理、命令模式等。
总结:成员指针在库设计中的优势
| 应用场景 | 使用的成员指针类型 | 核心价值 |
|---|---|---|
| 属性系统 | 数据成员指针 | 类型安全的成员访问封装,支持运行时动态访问 |
| ORM / 序列化 | 数据成员指针 | 存储偏移量,无需侵入式宏或代码生成,即可读写对象字段 |
| 信号槽 / 事件 | 成员函数指针 | 允许任意类的成员函数作为回调,解耦模块 |
| 泛型遍历 / 反射 | 数据成员指针元组 | 编译期遍历对象成员,实现通用算法(打印、哈希、比较) |
| 多态委托 | 成员函数指针 | 类型擦除 + 成员指针,实现类似 std::function 的轻量级委托 |
这些案例都体现了成员指针的核心理念:将"成员在类中的位置"作为一等公民 ,使得算法可以独立于具体对象,同时保持类型安全。在需要 运行时操作对象成员 且 编译期绑定类型 的场景下,成员指针是不可替代的工具。