如何用C++17标准库按列名和类型访问SQLite的SQLITE_ROW条目
用C++17标准库实现按列名访问的SQLite查询结果存储
核心方案:自定义行结构体 + std::variant + std::unordered_map
C++17的std::variant支持存储多种数据类型,搭配std::unordered_map<std::string, std::variant<...>>可以实现按列名索引不同类型的值。我们可以定义一个代表单条查询结果的结构体,再用std::vector存储所有行,就能满足你的需求。
具体实现步骤
1. 预获取列名与类型映射
在遍历查询结果前,先获取所有列的名称和对应SQLite数据类型,方便后续按类型解析数据:
// 先获取列数 int columnCount = sqlite3_column_count(stmt); // 存储列名到索引的映射 std::unordered_map<std::string, int> colNameToIndex; // 存储列的类型(可选,用于后续类型校验) std::vector<int> colTypes(columnCount); for (int i = 0; i < columnCount; ++i) { const char* colName = sqlite3_column_name(stmt, i); colNameToIndex[colName] = i; colTypes[i] = sqlite3_column_type(stmt, i); }
2. 定义自定义行类型
使用std::variant支持常见SQLite数据类型(整数、浮点数、字符串、NULL),并提供按列名访问的方法:
#include <unordered_map> #include <variant> #include <string> #include <stdexcept> #include <string> struct QueryRow { // 存储列名到对应值的映射,支持多种数据类型 std::unordered_map<std::string, std::variant<int, double, std::string, std::nullptr_t>> fields; // 模拟你想要的fieldByColumnName接口,返回可转换类型的包装 struct FieldWrapper { const std::variant<int, double, std::string, std::nullptr_t>& var; std::string AsString() const { if (std::holds_alternative<int>(var)) { return std::to_string(std::get<int>(var)); } else if (std::holds_alternative<double>(var)) { return std::to_string(std::get<double>(var)); } else if (std::holds_alternative<std::string>(var)) { return std::get<std::string>(var); } else if (std::holds_alternative<std::nullptr_t>(var)) { return "NULL"; } return ""; } }; FieldWrapper fieldByColumnName(const std::string& colName) const { auto it = fields.find(colName); if (it == fields.end()) { throw std::invalid_argument("Column not found: " + colName); } return FieldWrapper{it->second}; } // 额外提供直接获取原始类型的方法(可选) template<typename T> T get(const std::string& colName) const { auto it = fields.find(colName); if (it == fields.end()) { throw std::invalid_argument("Column not found: " + colName); } if (std::holds_alternative<T>(it->second)) { return std::get<T>(it->second); } else { throw std::bad_variant_access(); } } };
3. 遍历SQLite结果并填充数据
将每一行的数据按列名和对应类型存入自定义的QueryRow,再加入到结果vector中:
std::vector<QueryRow> resultRows; while (sqlite3_step(stmt) == SQLITE_ROW) { QueryRow row; for (const auto& [colName, index] : colNameToIndex) { int colType = sqlite3_column_type(stmt, index); switch (colType) { case SQLITE_INTEGER: row.fields[colName] = sqlite3_column_int(stmt, index); break; case SQLITE_FLOAT: row.fields[colName] = sqlite3_column_double(stmt, index); break; case SQLITE_TEXT: { const char* text = reinterpret_cast<const char*>(sqlite3_column_text(stmt, index)); row.fields[colName] = text ? std::string(text) : std::string(""); break; } case SQLITE_NULL: row.fields[colName] = nullptr; break; // 按需处理BLOB等其他类型 default: row.fields[colName] = std::string("Unsupported type"); break; } } resultRows.push_back(std::move(row)); }
4. 使用示例
按照你想要的方式访问数据:
for (const auto& row : resultRows) { // 用你指定的fieldByColumnName接口获取字符串形式的值 std::cout << row.fieldByColumnName("columnname").AsString() << '\n'; // 也可以直接获取原始类型(比如整数) try { int idVal = row.get<int>("id"); std::cout << "ID: " << idVal << '\n'; } catch (const std::exception& e) { std::cerr << "Error getting ID: " << e.what() << '\n'; } }
补充说明
std::variant是C++17新增特性,无需额外第三方库即可实现多类型存储。- 如果追求更高性能,可以将
std::unordered_map替换为预先维护的列名索引映射,用std::vector<std::variant<...>>存储行数据,内存更紧凑,但按列名访问的可读性会稍弱。 - 对于NULL值,这里用
std::nullptr_t标记,你也可以自定义空类型替代。
内容的提问来源于stack exchange,提问作者Namal
相关产品推荐
相关产品推荐

