1. 程式人生 > >有關 QString::toStdString() 使用的一個細節問題

有關 QString::toStdString() 使用的一個細節問題

當時的程式碼是這樣子的:

// 1
QString str = "Hello, world!";
char *cStr = str.toStdString().c_str();
// 2
std::string sstr = str.toStdString();
char *cStr2 = sstr.c_str();
// 3
func(str.toStdString().c_str());

我們主要關心的是幾個toStdString()函數出現的位置。前面兩個語句(第2行和第4、5行)看似是一樣的行為,其實不然。當你分別執行這兩段程式碼的時候會發現,第一段很可能會出現段錯誤,第二段卻能正常執行。而第一段和第三段又非常類似,不同之處在於前者是直接獲得一個char *

類似,後者則將其傳給一個函式作為引數。奇怪的是,原本第一段出錯的程式碼在第三段中卻是正常的。

第一段和第二段的區別之處在於,第一段程式碼直接呼叫了QString::toStdString()函式的返回值std::stringc_str()函式,第二段先將QString::toStdString()函式的返回值儲存下來,然後再呼叫其c_str()函式。

為了解釋前兩段程式碼執行效果的不同,我們需要檢查QString::toStdString()的簽名:

C++
1std::stringQString::toStdString()const;

這個函式返回一個與這個QString

內容相同的std::string物件。注意這個函式的返回值是一個物件。在 C++ 中,函式返回物件一般是類似下面的程式碼:

C++
1 2 3 4 5 6 clazzfoo() const { clazzc ; c.member=0; return c; }

注意這裡的返回物件,其實是一個臨時物件。在上面程式碼中,雖然我們在函式體內建立了一個clazz的物件c,但返回的並不是“這個”物件,而是由 C++ 建立一個臨時物件,再將這個臨時物件返回。注意這裡是“臨時物件”,臨時物件是有生命週期的。《C++ 程式設計語言》第 10 章中寫道,“除非一個臨時物件被約束到某個引用,或者被用於作為命名物件的初始化,否則它將在建立它的那個完整表示式結束時銷燬

”。所謂“完整表示式”,是指不是其它表示式的子表示式的表示式。簡單來說,一個完整表示式的標識一般是一個分號。

這句看似繞口的話解釋了之前所有的現象。在第一段程式碼中,由於函式返回一個臨時變數,我們立即呼叫了這個臨時物件的c_str()函式。這一切都沒有問題。之後,完整表示式結束(遇到分號),而這個臨時變數沒有賦值給某個引用或用於給某個物件初始化,所以這個臨時變數被立即銷燬。由此物件獲得的c_str()函式結果同樣被銷燬,因此發生段錯誤。在第二段程式碼中,這個臨時變數用於給sstr物件初始化,我們之後呼叫的是這個新的被初始化完成的物件的函式,也就是正常的。第三段程式碼雖然也沒有賦值給某個引用或用於給某個物件初始化,但在str.toStdString().c_str()語句結束後,表示式並沒有結束,而是繼續執行函式呼叫。直到函式呼叫返回,才遇到代表表示式結束的分號,此時臨時變數才會銷燬。而這時候我們已經成功執行了函式程式碼。所以一切都沒有問題。

至此我們明白了這種看似奇怪的現象其實只是一個 C++ 語言的陷阱,甚至與 Qt 沒有一點關係。同樣類似的陷阱還可能發生在QString::toUtf8()QString::toAscii()之類的函式身上。