More C++ Idioms/Temporary Proxy
Temporary Proxy
[edit | edit source]Intent
[edit | edit source]To detect and execute different code when the result of overloaded operator []
is either modified or observed.
Also Known As
[edit | edit source]operator []
proxy
Motivation
[edit | edit source]The index operator (operator []
) is often used to provide array like access syntax for user-defined classes. C++ standard library uses operator []
in std::string and std::map classes. Standard string simply returns a character reference as a result of operator []
whereas std::map returns a reference to the value given its key. In both cases, the returned reference can be directly read or written to. The string or map class have no knowledge or has no control over whether the reference is used for reading or for modification. Sometimes, however, it is useful to detect how the value is being used.
For example, consider an UndoString
class, which supports a single undo operation in addition to the existing std::string interface. The design must allow an undo even though the character is accessed using the index operator. As mention above, std::string class has no knowledge of whether the result of operator []
will be written to or not. The temporary proxy can be used to solve this problem.
Solution and Sample Code
[edit | edit source]The temporary proxy idiom uses another object, conveniently called proxy, to detect whether the result of operator []
is used for reading or writing. The UndoString
class below defines its own non-const operator []
, which takes place of std::string's non-const operator []
.
class UndoString : public std::string
{
struct proxy
{
UndoString * str;
size_t pos;
proxy(UndoString * us, size_t position)
: str(us), pos(position)
{}
// Invoked when proxy is used to modify the value.
void operator = (const char & rhs)
{
str->old = str->at(pos);
str->old_pos = pos;
str->at(pos) = rhs;
}
// Invoked when proxy is used to read the value.
operator const char & () const
{
return str->at(pos);
}
};
char old;
int old_pos;
public:
UndoString(const std::string & s)
: std::string(s), old(0), old_pos(-1)
{}
// This operator replaces std::string's non-const operator [].
proxy operator [] (size_t index)
{
return proxy(this, index);
}
using std::string::operator [];
void undo()
{
if(old_pos == -1)
throw std::runtime_error("Nothing to undo!");
std::string::at(old_pos) = old;
old = 0;
old_pos = -1;
}
};
The new operator []
returns an object of proxy
type. The proxy
type defines an overloaded assignment operator and a conversion operator. Depending on the context how the proxy is used, the compiler chooses different functions as shown below.
int main(void)
{
UndoString ustr("More C++ Idioms");
std::cout << ustr[0]; // Prints 'M'
ustr[0] = 'm'; // Change 'M' to 'm'
std::cout << ustr[0]; // Prints 'm'
ustr.undo(); // Restore'M'
std::cout << ustr[0]; // Prints 'M'
}
In all the output expressions (std::cout
) above, the proxy
object is used for reading and therefore, the compiler uses the conversion operator, which simply returns the underlying character. In the assignment statement (ustr[0] = 'm';
), however, the compiler invokes the assignment operator of the proxy
class. The assignment operator of the proxy
object saves the original character value in the parent UndoString
object and finally writes the new character value to the new position. This way, using an extra level of indirection of a temporary proxy object the idiom is able to distinguish between a read and a write operation and take different action based on that.
Caveats
Introducing an intermediary proxy
may result in surprising compiler errors. For example, a seemingly innocuous function modify
fails to compile using the current definition of the proxy
class.
void modify(char &c)
{
c = 'Z';
}
// ...
modify(ustr[0]);
The compiler is unable to find a conversion operator that converts the temporary proxy object into a char &
. We have defined only a const
conversion operator that returns a const char &
. To allow the program to compile, another non-const conversion operator could be added. However, as soon as we do that, it is not clear whether the result of conversion is used to modify the original or not.