簡體   English   中英

C ++隨機訪問迭代器,用於按需加載元素的容器

[英]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

這將使iter2iter指向相同的內容。

您遇到問題,因為您的迭代器不符合前向迭代器要求。 特別:

  • *i必須是對value_typeconst 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。

完整的演示

在線樣本使用源代碼行作為“消息”。

住在Coliru

#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; 
}; 

提升PropertyMap

您可以避免使用Boost PropertyMap編寫大量代碼:

住在Coliru

#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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM