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

2021-03-31 08:56:31 字數 3590 閱讀 5481

請看物件導向世界裡發生的一幕:

物件a:親愛的,永遠別變心!

物件b:別擔心,親愛的,我是const。

然而,和現實生活中一樣,a會懷疑,"能相信b嗎?" 同樣地,和現實生活中一樣,答案取決於b的本性:其成員函式的組成結構。

假設b是乙個const string物件:

class string ;

const string b("hello world");      // b是乙個const物件

既然b為const,最好的情況當然就是無論現在還是以後,b的值總是"hello world"。這就寄希望於別的程式設計師能以合理的方式使用b了。特別是,千萬別有什麼人象下面這樣殘忍地將b強制轉換掉const(參見條款21):

string& alsob =              // 使得alsob成為b的另乙個名字,

const_cast(b);    // 但不具有const屬性

然而,即使沒有人做這種殘忍的事,就能保證b永遠不會改變嗎?看看下面的情形:

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"

/   

/b.data

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

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

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

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

inline string::operator char*() const

這個函式既快又安全。雖然它和最初給出的那個函式不一樣,但它可以滿足大多數程式的需要。這個做法還和c++標準組織處理string/char*難題的方案一致:標準string型別中包含乙個成員函式c_str,它的返回值是string的const char*版本。關於標準string型別的更多資訊參見條款49。

指標並不是返回內部資料控制代碼的唯一途徑。引用也很容易被濫用。下面是一種常見的用法,還是拿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,或者重寫函式,使之不返回控制代碼。如果想讓string::operator既適用於const物件又適用於非const 物件,可以參見條款21。

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

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

string somefamousauthor()           // 隨機選擇乙個作家名

return "";                        // 程式不會執行到這兒,

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

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

}希望你的注意力不要集中在隨機數是怎樣從rand產生的問題上,也不要嘲笑我把自己和這些作家聯絡在一起。真正要注意的是,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物件被銷毀,其析構函式被呼叫。析構函式中,data指標被刪除(**詳見條款11)。然而,data和pc所指的是同一塊記憶體,所以現在pc指向的是被刪除的記憶體--------其內容是不可確定的。

因為pc是被乙個指向臨時物件的控制代碼初始化的,而臨時物件在被建立後又立即被銷毀,所以在pc被使用前控制代碼已經是非法的了。也就是說,無論想做什麼,當要使用pc時,pc其實已經名存實亡。這就是指向臨時物件的控制代碼所帶來的危害。

所以,對於const成員函式來說,返回控制代碼是不明智的,因為它會破壞資料抽象。對於非const成員函式來說,返回控制代碼會帶來麻煩,特別是涉及到臨時物件時。控制代碼就象指標一樣,可以是懸浮(dangle)的。所以一定要象避免懸浮的指標那樣,盡量避免懸浮的控制代碼。

同樣不能對本條款絕對化。在乙個大的程式中想消滅所有可能的懸浮指標是不現實的,想消滅所有可能的懸浮控制代碼也是不現實的。但是,只要不是萬不得已,就要避免返回控制代碼,這樣,不但程式會受益,使用者也會更信賴你。

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

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

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

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

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

假設b是乙個const string物件 class string const string b hello world b是乙個const物件 看看下面的情形 char str b 呼叫b.operator char strcpy str,hi mom 修改str指向的值 b的值現在還是 hell...