Reputation: 2260
I have a function which conceptually supposed to not modify the parameter. The parameter object is a big object (a vector with 10000 or more), so I don't want to create a copy. What is a moral way of doing this thing in c++.
double computeDelta(const vector< double > &grid, unsigned int index, double newvalue) {
// compute something on old grid
double oldvalue = grid[index]
// change grid temporarily
grid[index] = newvalue; // this is illegal because of const (How to do this is question)
// compute something on new grid
// restore original grid
grid[index] = oldvalue
return // difference of old thing and new thing
}
Upvotes: 2
Views: 137
Reputation: 67743
This is guaranteed perfectly safe, and is easy. Only do something more complex if profiling demonstrates you really need it
double computeDelta(vector<double> grid, unsigned int index, double newvalue) {
double before = compute(grid);
grid[index] = newvalue;
double after = compute(grid);
return after-before;
}
This requires the caller to trust you, and they may have to make a copy anyway if there are multiple threads
// I modify grid in-place, but promise to revert it before exiting
double computeDelta(vector<double> &grid, unsigned int index, double newvalue) {
double before = compute(grid);
// we can do something much more elegant if C++11 lambdas are allowed
struct swapper {
double &value;
double oldvalue;
swapper(double &v, double newvalue) : value(v), oldvalue(v) {
value = newvalue;
}
~swapper() { value = oldvalue; }
} guard(grid[index], newvalue);
double after = compute(grid);
return after-before;
}
This is the only safe (const-correct) way to take a const ref without forcing a copy. It requires that the computation is templated on the container type (or alternatively on the iterator type, and you proxy the iterator instead). Despite avoiding a copy, it may be slower depending on the access pattern
double computeDelta(vector<double> const &grid, unsigned int index, double newvalue) {
double before = compute(grid);
// assuming only operator[] is used by compute
struct overlay {
vector<double> const &base;
unsigned index;
double value;
overlay(vector<double> const &b, unsigned i, double v)
: base(b), index(i), value(v) {}
double operator[] (vector<double>::size_type i) const {
return (i == index) ? value : base[i];
}
vector<double>::size_type size() const { return base.size(); }
};
double after = compute(overlay(grid, index, newvalue));
return after-before;
}
Upvotes: 3
Reputation: 60778
Firstly, don't modify the vector - in your simple example I don't see why you can't just work in a temporary variable.
But const_cast is arguably the right thing to do here, given that this is a situation in which you need to judiciously "cheat" on const-ness. Note that this will break under multithreading, as your client code may assume you are not modifying the vector but you are. It will also break if you throw exceptions midway through, i.e., if you do not carefully guarantee atomicity.
Safest thing to do - just remove the const
declaration and explain how it is modified in comments, i.e., modified intermediately.
Upvotes: 2
Reputation: 1092
I don't think casting away the constness is a good idea. You could get rid of it, or if you want to keep the constness, write some adapter code to handle it for you. There are many ways you could do this. e.g.
As you iterate over the vector, if you get to index
, then you return your special value.
for (int i = 0; i < grid.size(); ++i) {
if(i == index) { /* do something with newvalue */ }
else { /* do something with grid[i] */ }
}
Or you could write a wrapper class to do the same thing
class GridWrapper {
public:
GridWrapper(const std::vector<double>& grid, unsigned int idx, double val)
: m_grid(grid), m_idx(idx), m_val(val) {}
double& operator[](unsigned int pos) {
if (pos == m_idx) return val;
else return m_grid[pos];
}
};
Or you could use something like boost::transform_iterator
to do the same thing.
Upvotes: 0
Reputation: 9354
Remove the const
and be very careful to clean up after yourself.
If you're taking a const
reference parameter, you are guaranteeing not to change it. If you start changing it, firstly this may well break horribly in a multi threaded program and secondly, ot is also possible that the vector is in some read only space [note: I'm aware this is quite tricky with C++ technology at the moment, but I could see a C++11 compiler working out it could create an initialised vector in read only space] - in which case calling your function will crash. Really, it would be better if it did not compile.
Upvotes: 0
Reputation: 688
You can use const_cast:
double computeDelta(const vector< double > &grid, unsigned int index, double newvalue) {
// compute something on old grid
double oldvalue = grid[index];
// change grid temporarily
const_cast<vector<double>&>(grid)[index] = newvalue;
// restore original grid
const_cast<vector<double>&>(grid)[index] = oldvalue;
return // difference of old thing and new thing
}
Upvotes: 2