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
}
};
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.
Use the mutable keyword on the elems_ data member.
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).
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.
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_)
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.