[英]C++ random access iterators for containers with elements loaded on demand
我目前正在開發一個小項目,需要從文件加載消息。 消息按順序存儲在文件中,文件可能會變得很大,因此將整個文件內容加載到內存中是不值得的。
因此,我們決定實現一個FileReader
類,它能夠快速移動到文件中的特定元素並根據請求加載它們。 常用的東西如下
SpecificMessage m;
FileReader fr;
fr.open("file.bin");
fr.moveTo(120); // Move to Message #120
fr.read(&m); // Try deserializing as SpecificMessage
FileReader本身效果很好。 因此,我們考慮添加符合STL的迭代器支持:一個隨機訪問迭代器,它提供對特定消息的只讀引用。 以下列方式使用
for (auto iter = fr.begin<SpecificMessage>(); iter != fr.end<SpecificMessage>(); ++iter) {
// ...
}
備注:以上假設該文件僅包含SpecialMessage類型的消息。 我們一直在使用boost::iterator_facade
來簡化實現。
現在我的問題歸結為:如何正確實現迭代器? 由於FileReader
實際上並不在內部保存一系列消息,而是根據請求加載它們。
到目前為止我們嘗試過的:
將消息存儲為迭代器成員
此方法將消息存儲在迭代器實例中。 這對於簡單的用例非常有用,但對於更復雜的用途卻失敗了。 例如, std::reverse_iterator
有一個看起來像這樣的解除引用操作
reference operator*() const
{ // return designated value
_RanIt _Tmp = current;
return (*--_Tmp);
}
這會破壞我們的方法,因為返回來自臨時迭代器的消息的引用。
使引用類型等於值類型
注釋中的@DDrmmr建議使引用類型等於值類型,以便返回內部存儲對象的副本。 但是,我認為這對於實現 - >運算符的反向迭代器無效
pointer operator->() const {
return (&**this);
}
derefs本身,調用*運算符,然后返回臨時的副本,最后返回此臨時的地址。
從外部存儲消息
或者我可以在外部存儲消息:
SpecificMessage m;
auto iter = fr.begin<SpecificMessage>(&m);
// ...
這似乎也有缺陷
auto iter2 = iter + 2
這將使iter2
和iter
指向相同的內容。
您遇到問題,因為您的迭代器不符合前向迭代器要求。 特別:
*i
必須是對value_type
或const value_type
([forward.iterators] /1.3)的左值引用 *i
不能引用存儲在迭代器本身中的對象,因為當且僅當它們綁定到同一個對象時,要求兩個迭代器相等([forward.iterators] / 6) 是的,這些要求在對接中是一個巨大的痛苦,是的,這意味着像std::vector<bool>::iterator
這樣的東西不是隨機訪問迭代器,即使某些標准庫實現錯誤地聲稱它們是。
編輯:以下建議的解決方案可怕地破解,因為解除引用臨時迭代器返回對對象的引用,該對象在使用引用之前可能不會生效。 例如, auto& foo = *(i + 1);
foo
引用的對象可能已被釋放。 OP中引用的reverse_iterator
的實現將導致相同的問題。
我建議你將你的設計分成兩個類:
FileCache
,它包含文件資源和緩存的加載消息;
FileCache::iterator
,它包含一個消息號,並在取消引用時懶惰地從
FileCache
檢索它。
實現可能就像在
FileCache
存儲
weak_ptr<Message>
的容器和在迭代器中存儲
shared_ptr<Message>
簡單 :
簡單演示
正如我在其他答案中暗示的那樣,您可以考慮使用內存映射文件。 在評論中你問:
就內存映射文件而言,這似乎不是我想要的,因為你如何為它們提供一個迭代器而不是SpecificMessages呢?
好吧,如果您的SpecificMessage是POD類型,您可以直接迭代原始內存。 如果沒有,您可以使用反序列化幫助程序(如您所知)並使用Boost transform_iterator
按需執行反序列化。
請注意,我們可以管理內存映射文件,這實際上意味着您可以將其用作常規堆,並且可以存儲所有標准容器。 這包括基於節點的容器( map<>
,例如),動態大小的容器(例如vector<>
)以及固定大小的容器( array<>
) - 以及它們的任何組合。
這是一個演示,它接受一個包含字符串的簡單SpecificMessage
,並且(de)將其直接派生到共享內存中:
using blob_t = shm::vector<uint8_t>;
using shared_blobs = shm::vector<blob_t>;
您感興趣的部分將是消費部分:
bip::managed_mapped_file mmf(bip::open_only, DBASE_FNAME);
shared_blobs* table = mmf.find_or_construct<shared_blobs>("blob_table")(mmf.get_segment_manager());
using It = boost::transform_iterator<LazyLoader<SpecificMessage>, shared_blobs::const_reverse_iterator>;
// for fun, let's reverse the blobs
for (It first(table->rbegin()), last(table->rend()); first < last; first+=13)
std::cout << "blob: '" << first->contents << "'\n";
// any kind of random access is okay, though:
auto random = rand() % table->size();
SpecificMessage msg;
load(table->at(random), msg);
std::cout << "Random blob #" << random << ": '" << msg.contents << "'\n";
因此,這將以相反的順序打印每個第13條消息,然后是隨機blob。
在線樣本使用源代碼行作為“消息”。
#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <iostream>
#include <boost/iterator/transform_iterator.hpp>
#include <boost/range/iterator_range.hpp>
static char const* DBASE_FNAME = "database.map";
namespace bip = boost::interprocess;
namespace shm {
using segment_manager = bip::managed_mapped_file::segment_manager;
template <typename T> using allocator = boost::container::scoped_allocator_adaptor<bip::allocator<T, segment_manager> >;
template <typename T> using vector = bip::vector<T, allocator<T> >;
}
using blob_t = shm::vector<uint8_t>;
using shared_blobs = shm::vector<blob_t>;
struct SpecificMessage {
// for demonstration purposes, just a string; could be anything serialized
std::string contents;
// trivial save/load serialization code:
template <typename Blob>
friend bool save(Blob& blob, SpecificMessage const& msg) {
blob.assign(msg.contents.begin(), msg.contents.end());
return true;
}
template <typename Blob>
friend bool load(Blob const& blob, SpecificMessage& msg) {
msg.contents.assign(blob.begin(), blob.end());
return true;
}
};
template <typename Message> struct LazyLoader {
using type = Message;
Message operator()(blob_t const& blob) const {
Message result;
if (!load(blob, result)) throw std::bad_cast(); // TODO custom excepion
return result;
}
};
///////
// for demo, create some database contents
void create_database_file() {
bip::file_mapping::remove(DBASE_FNAME);
bip::managed_mapped_file mmf(bip::open_or_create, DBASE_FNAME, 1ul<<20); // Even sparse file size is limited on Coliru
shared_blobs* table = mmf.find_or_construct<shared_blobs>("blob_table")(mmf.get_segment_manager());
std::ifstream ifs("main.cpp");
std::string line;
while (std::getline(ifs, line)) {
table->emplace_back();
save(table->back(), SpecificMessage { line });
}
std::cout << "Created blob table consisting of " << table->size() << " blobs\n";
}
///////
void display_random_messages() {
bip::managed_mapped_file mmf(bip::open_only, DBASE_FNAME);
shared_blobs* table = mmf.find_or_construct<shared_blobs>("blob_table")(mmf.get_segment_manager());
using It = boost::transform_iterator<LazyLoader<SpecificMessage>, shared_blobs::const_reverse_iterator>;
// for fun, let's reverse the blobs
for (It first(table->rbegin()), last(table->rend()); first < last; first+=13)
std::cout << "blob: '" << first->contents << "'\n";
// any kind of random access is okay, though:
auto random = rand() % table->size();
SpecificMessage msg;
load(table->at(random), msg);
std::cout << "Random blob #" << random << ": '" << msg.contents << "'\n";
}
int main()
{
#ifndef CONSUMER_ONLY
create_database_file();
#endif
srand(time(NULL));
display_random_messages();
}
我不得不承認,我可能不完全理解你把當前的MESSAGE作為Iter成員所遇到的麻煩。 我會將每個迭代器與它應該讀取的FileReader相關聯,並將其實現為FileReader的讀取索引的輕量級封裝::(read | moveTo)。 overwtite最重要的方法是boost::iterator_facade<...>::advance(...)
,它修改當前索引並嘗試從FileReader中提取新的MESSAGE如果失敗則將迭代器標記為無效並且解除引用將失敗。
template<class MESSAGE,int STEP>
class message_iterator;
template<class MESSAGE>
class FileReader {
public:
typedef message_iterator<MESSAGE, 1> const_iterator;
typedef message_iterator<MESSAGE,-1> const_reverse_iterator;
FileReader();
bool open(const std::string & rName);
bool moveTo(int n);
bool read(MESSAGE &m);
// get the total count of messages in the file
// helps us to find end() and rbegin()
int getMessageCount();
const_iterator begin() {
return const_iterator(this,0);
}
const_iterator end() {
return const_iterator(this,getMessageCount());
}
const_reverse_iterator rbegin() {
return const_reverse_iterator(this,getMessageCount()-1);
}
const_reverse_iterator rend() {
return const_reverse_iterator(this,-1);
}
};
// declaration of message_iterator moving over MESSAGE
// STEP is used to specify STEP size and direction (e.g -1 == reverse)
template<class MESSAGE,int STEP=1>
class message_iterator
: public boost::iterator_facade<
message_iterator<MESSAGE>
, const MESSAGE
, boost::random_access_traversal_tag
>
{
typedef boost::iterator_facade<
message_iterator<MESSAGE>
, const MESSAGE
, boost::random_access_traversal_tag
> super;
public:
// constructor associates an iterator with its FileReader and a given position
explicit message_iterator(FileReader<MESSAGE> * p=NULL,int n=0): _filereader(p),_idx(n),_valid(false) {
advance(0);
}
bool equal(const message_iterator & i) const {
return i._filereader == _filereader && i._idx == _idx;
}
void increment() {
advance(+1);
}
void decrement() {
advance(-1);
}
// overwrite with central functionality. Move to a given relative
// postion and check wether the position can be read. If move/read
// fails we flag the iterator as incalid.
void advance(int n) {
_idx += n*STEP;
if(_filereader!=NULL) {
if( _filereader->moveTo( _idx ) && _filereader->read(_m)) {
_valid = true;
return;
}
}
_valid = false;
}
// Return a ref to the currently cached MESSAGE. Throw
// an acception if positioning at this location in advance(...) failes.
typename super::reference dereference() const {
if(!_valid) {
throw std::runtime_error("access to invalid pos");
}
return _m;
}
private:
FileReader<MESSAGE> * _filereader;
int _idx;
bool _valid;
MESSAGE _m;
};
您可以避免使用Boost PropertyMap編寫大量代碼:
#include <boost/property_map/property_map.hpp>
#include <boost/property_map/function_property_map.hpp>
using namespace boost;
struct SpecificMessage {
// add some data
int index; // just for demo
};
template <typename Message>
struct MyLazyReader {
typedef Message type;
std::string fname;
MyLazyReader(std::string fname) : fname(fname) {}
Message operator()(size_t index) const {
Message m;
// FileReader fr;
// fr.open(fname);
// fr.moveTo(index); // Move to Message
// fr.read(&m); // Try deserializing as SpecificMessage
m.index = index; // just for demo
return m;
}
};
#include <iostream>
int main() {
auto lazy_access = make_function_property_map<size_t>(MyLazyReader<SpecificMessage>("file.bin"));
for (int i=0; i<10; ++i)
std::cout << lazy_access[rand()%256].index << "\n";
}
樣本輸出是
103
198
105
115
81
255
74
236
41
205
您可以在共享vector<array<byte, N>>
, flat_map<size_t, std::vector<uint8_t> >
vector<array<byte, N>>
存儲索引 - > BLOB對象的映射。
所以,現在你只需要從myshared_map[index].data()
( begin()
和end()
反序列化, myshared_map[index].data()
BLOB大小變化)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.