You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何扩展数据库中存储的Person类且最小化系统改动?

问题描述

我正在开发一个简易数据库,从CSV文件读取Person类字段并写入vector中。但未来如果要给Person类新增字段,就得重写类构造函数,同时CSV读写函数也得对应修改。想找一种能以最小代码改动为Person类添加字段的方法。

我自己想到的方案是用map替代固定字段,存储<字段名, 值>键值对,读取CSV时调用setter而非构造函数。请问还有其他可行方案吗?


Person类代码

person::person(unsigned int _id, std::string &_fullname, std::string &_occupation)
    : id(_id), fullname(_fullname), occupation(_occupation) {

}

unsigned int person::getId() const {
    return id;
}

const std::string &person::getFullname() const {
    return fullname;
}

const std::string &person::getOccupation() const {
    return occupation;
}

void person::setOccupation(std::string &_occupation) {
    person::occupation = _occupation;
}

person_csv_converter类代码

person_csv_converter::person_csv_converter(const std::string &_csvFilename) {
    in.open(_csvFilename);

    if (in.is_open()) {
        std::cout << "CSV file was found!";
    } else {
        throw file_not_found_exception("File was not found or file type is incorrect!");
    }
}

person_csv_converter::~person_csv_converter() {
    in.close();
}

std::vector<std::string> person_csv_converter::readLines() {
    std::string line;
    std::vector<std::string> lines;

    while (std::getline(in, line)) {
        lines.push_back(line);
    }

    return lines;
}

std::vector<person> person_csv_converter::convert(const std::vector<std::string> &_csvLines) {
    std::vector<person> persons;

    //pseudo code:
    /*
     * for i in range(_csvLines.size()) {
     *     vector.append(new person(_csvLines.split(",")))
     * }
     */
}

可行解决方案

1. 你提到的map键值对方案

这种方案能直接减少字段新增时的改动:Person类改用std::map<std::string, std::string>存储字段,CSV读取时只需按表头键名存入map,不需要修改构造函数。但缺点是失去强类型检查,访问字段依赖字符串键名容易出错,且性能比直接访问成员变量差。

2. 手动模拟反射机制

C++原生无反射,但可以手动给Person类添加字段元数据,让CSV转换器通过遍历元数据完成字段读写。新增字段时仅需更新元数据,无需修改构造函数和转换器核心逻辑。

示例实现思路

// 字段类型枚举
enum class FieldType { UINT, STRING };

// 字段描述结构体,包含名称、类型、赋值逻辑
struct FieldDesc {
    std::string name;
    FieldType type;
    std::function<void(person&, const std::string&)> setter;
};

class person {
private:
    unsigned int id;
    std::string fullname;
    std::string occupation;
public:
    // 使用默认构造函数,新增字段无需修改
    person() = default;

    // 静态字段元数据列表
    static const std::vector<FieldDesc> fields;

    // 各字段的setter
    void setId(unsigned int val) { id = val; }
    void setFullname(const std::string& val) { fullname = val; }
    void setOccupation(const std::string& val) { occupation = val; }

    // getter保持不变
    unsigned int getId() const { return id; }
    const std::string& getFullname() const { return fullname; }
    const std::string& getOccupation() const { return occupation; }
};

// 初始化字段元数据
const std::vector<FieldDesc> person::fields = {
    {"id", FieldType::UINT, [](person& p, const std::string& val) { p.setId(std::stoul(val)); }},
    {"fullname", FieldType::STRING, [](person& p, const std::string& val) { p.setFullname(val); }},
    {"occupation", FieldType::STRING, [](person& p, const std::string& val) { p.setOccupation(val); }}
};

转换器修改后的convert函数

// 先实现一个字符串分割函数
std::vector<std::string> split(const std::string& s, char delim) {
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream tokenStream(s);
    while (std::getline(tokenStream, token, delim)) {
        tokens.push_back(token);
    }
    return tokens;
}

std::vector<person> person_csv_converter::convert(const std::vector<std::string> &_csvLines) {
    std::vector<person> persons;
    if (_csvLines.empty()) return persons;

    // 解析表头
    std::vector<std::string> headers = split(_csvLines[0], ",");

    for (size_t i = 1; i < _csvLines.size(); ++i) {
        std::vector<std::string> values = split(_csvLines[i], ",");
        person p;

        // 根据表头匹配字段并赋值
        for (size_t j = 0; j < headers.size(); ++j) {
            auto it = std::find_if(person::fields.begin(), person::fields.end(),
                [&headers, j](const FieldDesc& fd) { return fd.name == headers[j]; });
            if (it != person::fields.end() && j < values.size()) {
                it->setter(p, values[j]);
            }
        }
        persons.push_back(p);
    }
    return persons;
}

新增字段时,仅需在Person类中添加成员变量、setter,并在person::fields中新增一条字段描述即可,转换器代码完全无需修改。

3. 使用第三方序列化/CSV库

比如Boost.Serialization、nlohmann/json(可配合CSV解析)、FastCSV等,这类库通过宏或注解标记类字段,自动处理序列化/反序列化逻辑。新增字段时仅需添加对应标记,无需手动修改读写代码。

以nlohmann/json配合CSV为例:

// 给Person类添加JSON序列化支持
namespace nlohmann {
    template<> struct adl_serializer<person> {
        static void to_json(json& j, const person& p) {
            j = json{{"id", p.getId()}, {"fullname", p.getFullname()}, {"occupation", p.getOccupation()}};
        }

        static void from_json(const json& j, person& p) {
            p.setId(j.at("id").get<unsigned int>());
            p.setFullname(j.at("fullname").get<std::string>());
            p.setOccupation(j.at("occupation").get<std::string>());
        }
    };
}

CSV读取时,先将每行数据按表头映射为JSON对象,再通过json.get<person>()完成对象转换。新增字段仅需修改to_json和from_json函数。

4. C++元编程字段映射

利用C++11+的元组、可变参数模板特性,将Person的字段打包为元组,通过编译期遍历元组完成CSV读写。这种方案无运行时开销,新增字段仅需修改元组定义。

示例思路

class person {
public:
    unsigned int id;
    std::string fullname;
    std::string occupation;

    // 将字段打包为元组,供元编程遍历
    auto as_tuple() { return std::tie(id, fullname, occupation); }
    auto as_tuple() const { return std::tie(id, fullname, occupation); }
};

// 表头与字段的映射
const std::vector<std::string> person_field_names = {"id", "fullname", "occupation"};

编写通用模板函数遍历元组完成赋值,新增字段时仅需修改as_tuple和person_field_names,转换器模板函数无需改动。


总结
  • 无第三方库且追求最小改动:手动模拟反射方案最优
  • 允许引入第三方库:序列化/CSV库能大幅减少重复代码
  • 性能敏感场景:元编程字段映射方案更适合
  • 追求极致灵活:你提到的map方案可选,但需接受类型安全和性能损失

内容的提问来源于stack exchange,提问作者nokkov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 12:35:39