有關 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::string
的c_str()
函式,第二段先將QString::toStdString()
函式的返回值儲存下來,然後再呼叫其c_str()
函式。
為了解釋前兩段程式碼執行效果的不同,我們需要檢查QString::toStdString()
的簽名:
1 | std::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()
之類的函式身上。