M
Mark Stijnman
A while ago I posted a question about how to get operator[] behave
differently for reading and writing. I basically wanted to make a
vector that can be queried about whether it is modified recently or
not. My first idea, using the const and non-const versions of
operator[], was clearly not correct, as was pointed out. Julián Albo
suggested I could use proxies to do that. I've done some googling for
proxies (also in this group) and personally, I think this issue should
go into the FAQ. It seems to have come up quite a few times (sometimes
in slightly different contexts) and the subject is complex enough to
warrant an easily found description of possible solutions and caveats.
Anyway, I came up with this implementation of a vector that counts the
changes made:
#include <vector>
class ChangeCountingVector {
public:
// constructor:
ChangeCountingVector(const int n=1): itsVector(n), itsChangeCounter(0)
{}
// return size:
unsigned int size() const { return itsVector.size(); }
// return number of changes since initialization:
unsigned long changes() const { return itsChangeCounter; }
// Proxy subclass for index operator with different read/write
behaviour:
class Proxy {
public:
// convert to double operator:
operator double() const { return itsCCVec.get(itsIndex); }
// assign from double:
const Proxy& operator=(const double newVal) const {
itsCCVec.put(itsIndex, newVal);
return *this;
}
// assign from a different proxy (so a = b works).
// Can't rely on the compiler generated default here:
const Proxy& operator=(const Proxy& p) const {
itsCCVec.put(itsIndex, p.itsCCVec.get(p.itsIndex));
return *this;
}
private:
// Constructor is private to prevent instantiating Proxy
// objects by other classes:
Proxy(ChangeCountingVector& theVec, int theIndex):
itsCCVec(theVec), itsIndex(theIndex) {}
// Owning class should be friend to be the only one allowed to
// instantiate Proxy objects:
friend class ChangeCountingVector;
// operator& is private so address can't be taken of a proxy object:
double* operator&() const ;
// reference to the vector:
ChangeCountingVector& itsCCVec;
// the index represented by the proxy:
int itsIndex;
};
const double operator[](const unsigned index) const {
return get(index);
}
Proxy operator[](const unsigned index) {
return Proxy(*this,index);
}
double get(const int index) const {
return itsVector[index];
}
double put(const int index, const double newVal) {
itsVector[index] = newVal;
++itsChangeCounter;
}
private:
std::vector<double> itsVector;
unsigned long itsChangeCounter;
};
Some benchmarking showed that this is fairly efficient. I used a
reference to the main vector in the Proxy class, instead of a pointer,
since it seemed to be slightly more efficient. I suspect that the
compiler can do a few more optimizations in that case. When all
compiler optimizations were used, g++ produced code that was comparable
in performance to a naive implementation, i.e. incrementing the counter
in the non-const operator[], regardless of whether it was writing or
only reading. Of course, this naive implementation will give incorrect
results - on reading, the vector will report being changed, but as a
performance comparison it served well enough.
On a side note: Obviously, incrementing the change counter on accessing
every single element is not the most efficient. In reality one would
add methods/operators to manipulate the vector as a whole, and count
that as one update. That's more efficient for processor optimizations
as well. Also, the Proxy class could use +=, -=, *= and /= operators
too.
My question: in most of the posts that I have found on Google
newsgroups and other sources on the internet in general, I have not
seen the "const Proxy& operator=(const Proxy& p) const" operator, just
an assignment operator that takes a double as an argument (or a
reference to a template object). But without this operator, the
compiler reports that it can't use the default assignment constructor
because of the presence of a non-static reference member. Can someone
please confirm that the code for this class is correct and that I'm not
doing anything potentially harmful in my reference juggling? Any
improvements that should be made? Thanks in advance,
regards Mark
differently for reading and writing. I basically wanted to make a
vector that can be queried about whether it is modified recently or
not. My first idea, using the const and non-const versions of
operator[], was clearly not correct, as was pointed out. Julián Albo
suggested I could use proxies to do that. I've done some googling for
proxies (also in this group) and personally, I think this issue should
go into the FAQ. It seems to have come up quite a few times (sometimes
in slightly different contexts) and the subject is complex enough to
warrant an easily found description of possible solutions and caveats.
Anyway, I came up with this implementation of a vector that counts the
changes made:
#include <vector>
class ChangeCountingVector {
public:
// constructor:
ChangeCountingVector(const int n=1): itsVector(n), itsChangeCounter(0)
{}
// return size:
unsigned int size() const { return itsVector.size(); }
// return number of changes since initialization:
unsigned long changes() const { return itsChangeCounter; }
// Proxy subclass for index operator with different read/write
behaviour:
class Proxy {
public:
// convert to double operator:
operator double() const { return itsCCVec.get(itsIndex); }
// assign from double:
const Proxy& operator=(const double newVal) const {
itsCCVec.put(itsIndex, newVal);
return *this;
}
// assign from a different proxy (so a = b works).
// Can't rely on the compiler generated default here:
const Proxy& operator=(const Proxy& p) const {
itsCCVec.put(itsIndex, p.itsCCVec.get(p.itsIndex));
return *this;
}
private:
// Constructor is private to prevent instantiating Proxy
// objects by other classes:
Proxy(ChangeCountingVector& theVec, int theIndex):
itsCCVec(theVec), itsIndex(theIndex) {}
// Owning class should be friend to be the only one allowed to
// instantiate Proxy objects:
friend class ChangeCountingVector;
// operator& is private so address can't be taken of a proxy object:
double* operator&() const ;
// reference to the vector:
ChangeCountingVector& itsCCVec;
// the index represented by the proxy:
int itsIndex;
};
const double operator[](const unsigned index) const {
return get(index);
}
Proxy operator[](const unsigned index) {
return Proxy(*this,index);
}
double get(const int index) const {
return itsVector[index];
}
double put(const int index, const double newVal) {
itsVector[index] = newVal;
++itsChangeCounter;
}
private:
std::vector<double> itsVector;
unsigned long itsChangeCounter;
};
Some benchmarking showed that this is fairly efficient. I used a
reference to the main vector in the Proxy class, instead of a pointer,
since it seemed to be slightly more efficient. I suspect that the
compiler can do a few more optimizations in that case. When all
compiler optimizations were used, g++ produced code that was comparable
in performance to a naive implementation, i.e. incrementing the counter
in the non-const operator[], regardless of whether it was writing or
only reading. Of course, this naive implementation will give incorrect
results - on reading, the vector will report being changed, but as a
performance comparison it served well enough.
On a side note: Obviously, incrementing the change counter on accessing
every single element is not the most efficient. In reality one would
add methods/operators to manipulate the vector as a whole, and count
that as one update. That's more efficient for processor optimizations
as well. Also, the Proxy class could use +=, -=, *= and /= operators
too.
My question: in most of the posts that I have found on Google
newsgroups and other sources on the internet in general, I have not
seen the "const Proxy& operator=(const Proxy& p) const" operator, just
an assignment operator that takes a double as an argument (or a
reference to a template object). But without this operator, the
compiler reports that it can't use the default assignment constructor
because of the presence of a non-static reference member. Can someone
please confirm that the code for this class is correct and that I'm not
doing anything potentially harmful in my reference juggling? Any
improvements that should be made? Thanks in advance,
regards Mark