views:

305

answers:

6

I wrote a little "lazy vector" class (or, delayed vector) which is supposed to look like a std::vector and usable wherever a std::vector is used, but it loads its elements "lazily", i.e. it will load element n (and possibly a few more) from disk whenever someone accesses element n. (The reason is that in my app, not all elements fit into memory.)

Here is this LazyVector class, but there is a problem with const member functions that use such a vector, see below.

template<class T>
class LazyVector {
  std::vector<T> elems_;
  void fetchElem(unsigned n){
    // load the n-th elem from disk into elems_ etc
  }
public:
  const T& operator[](unsigned n) const {
    fetchElem(n); // ERROR: ... discards qualifiers       
    return elems_[n];
  }
  T& operator[](unsigned n) {
    fetchElem(n);
    return elems_[n];
  }
  // and provide some other std::vector functions
};

As I said, there is a problem when a const member function asks for an element of the LazyVector. By nature of the LazyVector, accessing an element is not const, i.e. it will change the vector vec below, which is forbidden in this context. The foo member function must be const and cannot be changed. How can I solve this?

class Foo {
  LazyVector<const std::string*> vec;
  void fct(int n) const { // fct must be const 
    const std::string* str = vec[n];
    // do something with str 
  }
};
+4  A: 

Use the mutable keyword on the elems_ data member.

Miles
But even if I use mutable on the elems_ data member, the const operator[] is still not allowed to call fetchElem(). It may now directly manipulate elems_, but not call some non-const function?!
dehmann, right. the fetchElem function has to be const too. that makes sense, because it does not change the visible state of your object.
Johannes Schaub - litb
Oops, just realized that too. Thanks.
+1  A: 

Declare elems_ as mutable:

mutable std::vector<T> elems_;

There's other stuff you can do, but that's the supported way of doing it.

Edit: Another way of doing this is to add another member and set it in the constructor:

std::vector<T> *mutable_elems_;

mutable_elems_(&elems_)

MSN
+2  A: 

For such things, the mutable keyword is for. Put your cache as a mutable object into your class. That is because your cache seems to not change the logical content/state of your object (i.e the elements of your vector or the size of it do not change).

const methods do not state they don't physically change your object. They state that they won't change the abstract value of your object. Implementation details that are abstracted away may still be changed by const functions.

The mutable is for this kind of cases. Make your vector mutable or add a mutable cache member that contains some sort of cache entries.

Read the What are the semantics of a const member function answer by Anthony Williams.

Johannes Schaub - litb
+5  A: 

You can either use mutable member data or const_cast in the implementation of your LazyVector class. Thus you can create the illusion of constness needed by your consuming class without actually being const.

GBegen
well, you need to make the member mutable anyway. casting away constness of "this" and call non-const member function is very dangerous: if you have a const lazy vector (LazyVector<std::string*> const obj = foo;), then it's undefined behavior to change any non-mutable members.
Johannes Schaub - litb
Private mutable members are the way to go, mutable was created for exactly this reason.
Roger Pate
Delegate the work to a cache. Pointer members are good for caches. The pointer needs to remain const but the object pointed at by the poiner does not need to be const.
Martin York
+1  A: 

A crude way of doing this would be

LazyVector* p = const_cast<LazyVector*>(this);
p->fetch();

I guess there will be better way of doing this. But this will work.

Naveen
+3  A: 

The const operator is used to show that the object is logically const.
The fact that your data is on disk is neither here nor there your object is not changing state so you can delegate the work for actually holding the data to another object a cache (Where the data is stored is an implementation details and not part of the objects state).

class LazyVector
{
    public:
        int const& operator[](int index) const
        { 
            data->fetchElement(index);
            return data->get(index);
        }
    private:
        std::auto_ptr<LazyDataCache>   data;
};

Here data is a pointer (a smart pointer but still a pointer). As long as the pointer does not change you are not changing the cost-ness of the LazyVector. But you can still call non const methods on the object pointed at by data (remember it is the pointer that is const NOT the object pointed at).

Martin York
The RIGHT answer. +1
Daniel Earwicker