跳至内容

更多 C++ 习语/临时代理

来自 Wikibooks,开放世界中的开放书籍

临时代理

[编辑 | 编辑源代码]

当重载的 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 转换运算符。但是,一旦我们这样做,就不清楚转换结果是否用于修改原始结果。

已知用法

[编辑 | 编辑源代码]
[编辑 | 编辑源代码]

参考文献

[编辑 | 编辑源代码]
华夏公益教科书