更多 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 轉換運算子。但是,一旦我們這樣做,就不清楚轉換結果是否用於修改原始結果。