更多 C++ 习语/临时代理
当重载的 operator []
的结果被修改或观察时,检测并执行不同的代码。
operator []
代理
索引运算符 (operator []
) 通常用于为用户定义的类提供类似数组的访问语法。C++ 标准库在 std::string 和 std::map 类中使用 operator []
。标准字符串简单地返回一个字符引用作为 operator []
的结果,而 std::map 返回给定其键的值的引用。在这两种情况下,返回的引用都可以直接读写。字符串或映射类不知道或无法控制引用是用于读取还是用于修改。但是,有时检测值的用途很有用。
例如,考虑一个 UndoString
类,它除了现有的 std::string 接口之外,还支持单个 撤消 操作。设计必须允许撤消,即使字符是使用索引运算符访问的。如上所述,std::string 类不知道 operator []
的结果是否将被写入。可以使用临时代理来解决此问题。
临时代理习语使用另一个对象,方便地称为 代理,来检测 operator []
的结果是用于读取还是写入。下面的 UndoString
类定义了自己的非 const operator []
,它代替了 std::string 的非 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;
}
};
新的 operator []
返回一个 proxy
类型的对象。proxy
类型定义了一个重载的赋值运算符和一个转换运算符。根据代理的使用方式,编译器选择不同的函数,如下所示。
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'
}
在所有上面的输出表达式 (std::cout
) 中,proxy
对象用于读取,因此,编译器使用转换运算符,它只返回底层字符。但是,在赋值语句 (ustr[0] = 'm';
) 中,编译器调用 proxy
类的赋值运算符。proxy
对象的赋值运算符在父 UndoString
对象中保存原始字符值,最后将新字符值写入新位置。通过这种方式,使用临时代理对象的额外间接级别,习语能够区分读操作和写操作,并根据此采取不同的操作。
注意事项
引入中间 proxy
可能会导致令人惊讶的编译错误。例如,一个看似无害的函数 modify
使用当前定义的 proxy
类无法编译。
void modify(char &c)
{
c = 'Z';
}
// ...
modify(ustr[0]);
编译器无法找到将临时代理对象转换为 char &
的转换运算符。我们只定义了一个返回 const char &
的 const
转换运算符。为了允许程序编译,可以添加另一个非 const 转换运算符。但是,一旦我们这样做,就不清楚转换结果是否用于修改原始结果。