std::stringインターフェース
も一つイケてないのは、datumとstd::stringとの変換でしょうか。GDBMとのキー/値の受け渡しはいずれもdatumを介して行うために、登録/検索のたびにdatumとstd::stringとの変換を要します。ここんとこを簡略化すべく、変換関数to_datum()とfrom_datum()を用意しましょう。
// gdbm_db.h(部分) std::string from_datum(const datum& d); datum to_datum(const std::string& s);
std::string from_datum(const datum& d) { std::string result; if ( d.dptr != nullptr ) result.assign(d.dptr, d.dsize); return result; } datum to_datum(const std::string& s) { datum result; result.dptr = const_cast<char*>(s.data()); result.dsize = s.size(); return result; }
これで利用者側コードはいくぶん涼しくなりますね。
#include <iostream> #include <cassert> #include <array> #include "gdbm_db.h" using namespace std; void store() { gdbm::db dbx("trial.db", 512, GDBM_NEWDB); assert( dbx.is_open() ); array<string,4> keys = { "apple", "orange", "grape", "apple" }; array<string,4> contents = { "まっく", "みかん", "ぶどう", "りんご" }; for ( size_t i = 0; i < keys.size(); ++i ) { int result = dbx.store(gdbm::to_datum(keys[i]), gdbm::to_datum(contents[i])); assert( result == 0 ); } cout << "3 entries stored." << endl; } void fetch() { gdbm::db dbx("trial.db", 512, GDBM_WRCREAT); assert( dbx.is_open() ); array<string,4> keys = { "apple", "orange", "grape", "peach" }; for ( const string& target : keys ) { datum key = gdbm::to_datum(target); gdbm::auto_datum content =dbx.fetch(key); if ( empty(content) ) { cout << target << " not found..." << endl; } else { cout << target << " = " << gdbm::from_datum(content) << endl; } } cout << "--- list all entries ---" << endl; gdbm::auto_datum key = dbx.firstkey(); while ( !empty(key) ) { gdbm::auto_datum content = dbx.fetch(key); cout << gdbm::from_datum(key) << " = " << gdbm::from_datum(content) << endl; key = dbx.nextkey(key); } } int main() try { store(); fetch(); } catch ( gdbm::db_error& ex ) { cout << ex.what() << endl; }
連想配列
これが最後のステップ、さきほどの変換関数をクラス・テンプレート:datum_converterに仕立て、さらにこのdatum_converterをデフォルト・テンプレート引数とするdb_map<K,V>を作ります(K:キー/V:値)。
// gdbm_db.h(部分) namespace gdbm { template<typename K, typename V, typename KC =datum_converter<K>, typename VC =datum_converter<V>> class db_map { public: db_map(db& d, KC kc=KC(), VC vc=VC()) : db_(d), kc_(kc), vc_(vc) {} int store(const K& key, const V& val, int flag =GDBM_REPLACE) { return db_.store(kc_.to_datum(key), vc_.to_datum(val), flag); } bool fetch(const K& key, V& val) { auto_datum d = db_.fetch(kc_.to_datum(key)); if ( !empty(d) ) { val = vc_.from_datum(d); } return !empty(d); } bool firstkey(K& key) { auto_datum d = db_.firstkey(); if ( !empty(d) ) { key = kc_.from_datum(d); } return !empty(d); } bool nextkey(K& key) { auto_datum d = db_.nextkey(kc_.to_datum(key)); if ( !empty(d) ) { key = kc_.from_datum(d); } return !empty(d); } class db_map_ref { public: db_map_ref(db_map& db, const K& key) : db_(db), key_(key) {} operator V() { V result; db_.fetch(key_,result); return result; } db_map_ref& operator=(const V& val) { db_.store(key_,val); return *this; } private: db_map& db_; const K& key_; }; db_map_ref operator[](const K& key) { return db_map_ref(*this,key); } private: db& db_; KC kc_; VC vc_; }; }
db_map<K,V>は連想配列をサポートしています。db_map<K,V>は内部クラスdb_map_refを返すoperator[](const K&) をメンバ関数に持っています。db_map_refはそれが右辺にくれば(operator V()によって)キーに紐づいた値を返し、左辺にくれば(operator =(const V&)によって)その右辺値をVとしてgdbmにstore()します。そんなわけで利用者コードはデータベースを配列であるかのように扱えます。
#include <iostream> #include <cassert> #include <array> #include "gdbm_db.h" using namespace std; void store() { gdbm::db dbx("trial.db", 512, GDBM_NEWDB); assert( dbx.is_open() ); gdbm::db_map<string,string> dmap(dbx); array<string,4> keys = { "apple", "orange", "grape", "apple" }; array<string,4> contents = { "まっく", "みかん", "ぶどう", "りんご" }; for ( size_t i = 0; i < keys.size(); ++i ) { dmap[keys[i]] = contents[i]; } cout << "3 entries stored." << endl; } void fetch() { gdbm::db dbx("trial.db", 512, GDBM_WRCREAT); assert( dbx.is_open() ); gdbm::db_map<string,string> dmap(dbx); array<string,4> keys = { "apple", "orange", "grape", "peach" }; for ( const string& key : keys ) { string content = dmap[key]; if ( !content.empty() ) { cout << key << " = " << content << endl; } else { cout << key << " not found..." << endl; } } cout << "--- list all entries ---" << endl; string key; string content; bool hasmore = dmap.firstkey(key); while ( hasmore ) { dmap.fetch(key, content); cout << key << " = " << content << endl; hasmore = dmap.nextkey(key); } } int main() try { store(); fetch(); } catch ( gdbm::db_error& ex ) { cout << ex.what() << endl; }
以上、C++の基本的な構文とテクニック(と呼べるほどのものではないけれど)を使ってC-interfaceから連想配列に仕立てるまでのオハナシでした。