條款29 避免返回內部資料的控制代碼

2022-03-17 19:40:57 字數 3308 閱讀 3969

假設b是乙個const string物件:

class

string

;const

string b("

hello world

"); //

b是乙個const物件

看看下面的情形:

char *str = b;               // 呼叫b.operator char*()

strcpy(str, "hi mom");       // 修改str指向的值

b的值現在還是"hello world"嗎?或者,它是否已經變成了對母親的問候語?答案完全取決於string::operator char*的實現。

下面是乙個有欠考慮的實現,它導致了錯誤的結果。但是,它工作起來確實很高效,所以很多程式設計師才掉進它的錯誤陷阱之中:

//

乙個執行很快但不正確的實現

inline string::operator

char*() const

這個函式的缺陷在於它返回了乙個"控制代碼"(在本例中,是個指標),而這個控制代碼所指向的資訊本來是應該隱藏在被呼叫函式所在的string物件的內部。這樣,這個控制代碼就給了呼叫者自由訪問data所指的私有資料的機會。換句話說,有了下面的語句:

char *str = b;

情況就會變成這樣:

str------------------------->"hello world\0"

顯然,任何對str所指向的記憶體的修改都使得b的有效值發生變化。所以,即使b宣告為const,而且即使只是呼叫了b的某個const成員函式,b也會在程式執行過程中得到不同的值。特別是,如果str修改了它所指的值,b也會改變。

string::operator char*本身寫的沒有一點錯,麻煩的是它可以用於const物件。如果這個函式不宣告為const,就不會有問題,因為這樣它就不能用於象b這樣的const物件了。(const物件不能呼叫非const成員函式)

但是,將乙個string物件轉換成它相應的char*形式是很合理的一件事,無論這個物件是否為const。所以,還是應該使函式保持為const。這樣的話,就得重寫這個函式,使得它不返回指向物件內部資料的控制代碼:

//

乙個執行慢但很安全的實現

inline string::operator

char*() const

這個實現很安全,因為它返回的指標所指向的資料只是string物件所指向資料的拷貝;通過函式返回的指標無法修改string物件的值。當然,安全是要有代價的:這個版本的string::operator char* 執行起來比前面那個簡單版本要慢;此外,函式的呼叫者還要記得delete掉返回的指標。

如果不能忍受這個版本的速度,或者擔心記憶體洩露,可以來一點小小的改動:使函式返回乙個指向const char的指標:

class

string

;inline

string::operator

const

char*() const

指標並不是返回內部資料控制代碼的唯一途徑。引用也很容易被濫用。下面是一種常見的用法,還是拿string類做例子:

class

string

private:

char *data;

};string s = "

i'm not constant";

s[0] = '

x'; //

正確, s不是const

const

string cs = "

i'm constant";

cs[0] = '

x'; //

修改了const string,

//但編譯器不會通知

注意string::operator是通過引用返回結果的。這意味著函式的呼叫者得到的是內部資料data[index]的另乙個名字,而這個名字可以用來修改const物件的內部資料。這個問題和前面看到的相同,只不過這次的罪魁禍首是引用,而不是指標。

這類問題的通用解決方案和前面關於指標的討論一樣:或者使函式為非const(const物件就不能呼叫該函式了),或者重寫函式,使之不返回控制代碼(返回const引用即可)。如果想讓string::operator既適用於const物件又適用於非const 物件,可以參見條款21。

並不是只有const成員函式需要擔心返回控制代碼的問題,即使是非const成員函式也得承認:控制代碼的合法性失效的時間和它所對應的物件是完全相同的。這個時間可能比使用者期望的要早很多,特別是當涉及的物件是由編譯器產生的臨時物件時。

例如,看看這個函式,它返回了乙個string物件:

string somefamousauthor()           //

隨機選擇乙個作家名

return

""; //

程式不會執行到這兒,

//但對於乙個有返回值的函式來說,

//任何執行途徑上都要有返回值

}

somefamousauthor的返回值是乙個string物件,乙個臨時string物件(參見條款m19)。這樣的物件是暫時性的,它們的生命週期通常在函式呼叫表示式結束時終止。例如上面的情況中,包含somefamousauthor函式呼叫的表示式結束時,返回值物件的生命週期也就隨之結束。

具體看看下面這個使用somefamousauthor的例子,假設string宣告了乙個上面的operator const char*成員函式:

const char *pc = somefamousauthor();

cout << pc;                  

不論你是否相信,誰也不能**這段**將會做些什麼,至少不能確定它會做些什麼。因為當你想列印pc所指的字串時,字串的值是不確定的。造成這一結果的原因在於pc初始化時發生了下面這些事件:

1. 產生乙個臨時string物件用以儲存somefamousauthor的返回值(預設拷貝建構函式,淺拷貝指標)。

2. 通過string的operator const

char*成員函式將臨時string物件轉換為const char*指標,並用這個指標初始化pc。

3. 臨時string物件被銷毀,其析構函式被呼叫(我感覺臨時物件沒有被銷毀,但是函式內部string物件被銷毀,而臨時物件淺拷貝指標,所以臨時物件的data指標指向無效的記憶體空間)。析構函式中,data指標被刪除(**詳見條款11)。然而,data和pc所指的是同一塊記憶體,所以現在pc指向的是被刪除的記憶體--------其內容是不可確定的。

避免返回內部資料的控制代碼

假設b是乙個const string物件 class string const string b hello world b是乙個const物件 既然b為const,最好的情況當然就是無論現在還是以後,b的值總是 hello world 這就寄希望於別的程式設計師能以合理的方式使用b了。特別是,千萬...

學習 避免返回內部資料的控制代碼

請看物件導向世界裡發生的一幕 物件a 親愛的,永遠別變心!物件b 別擔心,親愛的,我是const。然而,和現實生活中一樣,a會懷疑,能相信b嗎?同樣地,和現實生活中一樣,答案取決於b的本性 其成員函式的組成結構。假設b是乙個const string物件 class string const stri...

c 避免返回內部資料的控制代碼

c primer中說了,在乙個物件呼叫其成員函式時,它隱含的乙個形參this指標。例如,我們定義了乙個函式ctest ttt 實際上在編譯器中該函式的定義就是ctest ttt ctest const this 該this指標所指向的內容可以改變,但是該this指標不可以被改變。當我們用ctest的...