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から連想配列に仕立てるまでのオハナシでした。
