C ++随机访问迭代器,用于按需加载元素的容器

时间:2014-11-09 09:47:18

标签: c++ boost iterator std random-access

我目前正在开发一个小项目,需要从文件加载消息。消息按顺序存储在文件中,文件可能变得很大,因此将整个文件内容加载到内存中是没有用的。

因此我们决定实现一个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在评论中建议使引用类型等于值类型,以便返回内部存储对象的副本。但是,我认为这对于实现 - &gt;的反向迭代器无效。运营商

pointer operator->() const {
  return (&**this);
}
depps本身的

,调用*运算符,然后返回临时的副本,最后返回此临时的地址。

在外部存储消息

或者我可以在外部存储消息:

SpecificMessage m;
auto iter = fr.begin<SpecificMessage>(&m);
// ...

似乎也存在缺陷

auto iter2 = iter + 2

iter2iter指向相同的内容。

4 个答案:

答案 0 :(得分:2)

您遇到问题,因为您的迭代器不符合前向迭代器要求。具体做法是:

  • *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检索它。实现可能就像在weak_ptr<Message>中存储FileCache容器和在迭代器中存储shared_ptr<Message>一样简单: Simple demo

答案 1 :(得分:2)

正如我在其他答案中暗示的那样,您可以考虑使用内存映射文件。在评论中你问:

  

就内存映射文件而言,这似乎不是我想要的,因为你如何为它们提供一个迭代器而不是SpecialMessages?

好吧,如果您的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。

完整演示

在线样本使用来源行作为&#34;消息&#34;。

<强> Live On 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();
}

答案 2 :(得分:0)

我不得不承认,我可能不完全理解将当前的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; 
}; 

答案 3 :(得分:0)

Boost PropertyMap

您可以避免使用Boost PropertyMap编写大部分代码:

<强> Live On 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

使用内存映射文件

您可以存储索引地图 - &gt;共享vector<array<byte, N>>flat_map<size_t, std::vector<uint8_t> >或类似内容中的BLOB对象。

因此,现在您只需要从myshared_map[index].data()begin()end()反序列化,以防BLOB大小发生变化)