Let's say I have something like the following method in my container class:
Datatype& operator[](const unsigned int Index) // I know this should use size_t instead.
{
return *(BasePointer + Index); // Where BasePointer is the start of the array.
}
I'd like to implement some sort of bounds-checking for the MyInstance[Index] = Value
usage so the container resizes automatically if the user tries to change a value outside its range. However, I want something else to happen if the user tries to access a value outside the container's range, eg MyVariable = MyInstance[Index]
. How can I detect how operator[]
is being used?
Sketch:
return a proxy object instead of the actual data entry. The proxy object then defines operator =
to handle the assignment case, and an implicit conversion operator for the reading-out case.
template <typename T>
class AccessorProxy {
friend class Container<T>;
public:
AccessorProxy(Container<T>& data, unsigned index)
: data(data), index(index) { }
void operator =(T const& new_value) {
// Expand array.
}
operator const T&() const {
// Do bounds check.
return *(data.inner_array + index);
}
private:
AccessorProxy(const AccessorProxy& rhs)
: data(rhs.data), index(rhs.index) {}
AccessorProxy& operator=(const AccessorProxy&);
Container<T>& data;
unsigned index;
};
template <typename T>
class ConstAccessorProxy {
friend class Container<T>;
public:
ConstAccessorProxy(const Container<T>& data, unsigned index)
: data(data), index(index) { }
operator const T&() const {
// Do bounds check.
return *(data.inner_array + index);
}
private:
ConstAccessorProxy(const ConstAccessorProxy& rhs)
: data(rhs.data), index(rhs.index) {}
ConstAccessorProxy& operator=(const ConstAccessorProxy&);
const Container<T>& data;
unsigned index;
};
AccessorProxy<Datatype> operator[](const unsigned int Index)
{
return AccessorProxy<Datatype>(*this, Index);
}
ConstAccessorProxy<Datatype> operator[] const (const unsigned int Index)
{
return ConstAccessorProxy<Datatype>(*this, Index);
}
The accessor classes will likely need to be be friends of the container class.
Finding ways to avoid the code duplication is left as an exercise to the reader. :)
Use a dummy class type to represent expressions like MyInstance[Index]
and delay figuring out what to do until that expression is used.
class MyContainer {
private:
class IndexExpr {
public:
// Get data from container:
operator const Datatype&() const;
// Expand container if necessary, then store data:
Datatype& operator=(const Datatype& value);
// Treat MyInstance[i] = MyInstance[j]; as expected:
Datatype& operator=(const IndexExpr& rhs)
{ return *this = static_cast<const Datatype&>(rhs); }
private:
IndexExpr(MyContainer& cont, unsigned int ind);
MyContainer& container_;
unsigned int index_;
friend class MyContainer;
};
public:
IndexExpr operator[](unsigned int Index)
{ return IndexExpr(*this, Index); }
// No IndexExpr needed when container is const:
const Datatype& operator[](unsigned int Index) const;
// ...
};
This is not a perfect answer to "how to detect", but, if the user is accessing the operator[]
via a const instance, then throw an exception if the index is out of bounds. ie
Datatype const& operator[]() const { .. // don't modify here, throw exception
However, if the user is accessing the instance via a non const instance, then by all means expand if the index is out of bounds (and within your acceptable ranges)
Datatype& operator[]() { .. // modify here
Basically, you are using the const attribute of the instance to determine what your semantics would be (as done in std::map
- ie trying to call operator[]
on a const instance of a map results in a compiler error - ie there is no const qualified operator[]
for map, because the function is guaranteed to create a mapping if the key does not exist already.)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.