C++成员指针在库设计中的实际案例

成员指针(数据成员指针和成员函数指针)在库设计中常用于实现 编译期类型安全运行时动态访问 以及 泛型算法。下面通过几个实际案例展示其典型应用。


案例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::signals2Qt 的 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 的轻量级委托

这些案例都体现了成员指针的核心理念:将"成员在类中的位置"作为一等公民 ,使得算法可以独立于具体对象,同时保持类型安全。在需要 运行时操作对象成员编译期绑定类型 的场景下,成员指针是不可替代的工具。

相关推荐
小则又沐风a2 小时前
STL库(vector)逐步分析vector( 包含常用的接口的使用讲解)
开发语言·c++
故事和你9112 小时前
洛谷-数据结构1-1-线性表1
开发语言·数据结构·c++·算法·leetcode·动态规划·图论
脱氧核糖核酸__12 小时前
LeetCode热题100——53.最大子数组和(题解+答案+要点)
数据结构·c++·算法·leetcode
脱氧核糖核酸__12 小时前
LeetCode 热题100——42.接雨水(题目+题解+答案)
数据结构·c++·算法·leetcode
王老师青少年编程13 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【线性扫描贪心】:数列分段 Section I
c++·算法·编程·贪心·csp·信奥赛·线性扫描贪心
王老师青少年编程13 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【线性扫描贪心】:分糖果
c++·算法·贪心算法·csp·信奥赛·线性扫描贪心·分糖果
leaves falling13 小时前
C++模板进阶
开发语言·c++
无敌昊哥战神14 小时前
【保姆级题解】力扣17. 电话号码的字母组合 (回溯算法经典入门) | Python/C/C++多语言详解
c语言·c++·python·算法·leetcode
脱氧核糖核酸__14 小时前
LeetCode热题100——238.除了自身以外数组的乘积(题目+题解+答案)
数据结构·c++·算法·leetcode