有关 QString::toStdString() 使用的一个细节问题

当时的代码是这样子的:

我们主要关心的是几个toStdString()函数出现的位置。前面两个语句(第2行和第4、5行)看似是一样的行为,其实不然。当你分别运行这两段代码的时候会发现,第一段很可能会出现段错误,第二段却能正常运行。而第一段和第三段又非常类似,不同之处在于前者是直接获得一个char *类似,后者则将其传给一个函数作为参数。奇怪的是,原本第一段出错的代码在第三段中却是正常的。

第一段和第二段的区别之处在于,第一段代码直接调用了QString::toStdString()函数的返回值std::stringc_str()函数,第二段先将QString::toStdString()函数的返回值保存下来,然后再调用其c_str()函数。

为了解释前两段代码执行效果的不同,我们需要检查QString::toStdString()的签名:

这个函数返回一个与这个QString内容相同的std::string对象。注意这个函数的返回值是一个对象。在 C++ 中,函数返回对象一般是类似下面的代码:

注意这里的返回对象,其实是一个临时对象。在上面代码中,虽然我们在函数体内创建了一个clazz的对象c,但返回的并不是“这个”对象,而是由 C++ 创建一个临时对象,再将这个临时对象返回。注意这里是“临时对象”,临时对象是有生命周期的。《C++ 程序设计语言》第 10 章中写道,“除非一个临时对象被约束到某个引用,或者被用于作为命名对象的初始化,否则它将在创建它的那个完整表达式结束时销毁”。所谓“完整表达式”,是指不是其它表达式的子表达式的表达式。简单来说,一个完整表达式的标识一般是一个分号。

这句看似绕口的话解释了之前所有的现象。在第一段代码中,由于函数返回一个临时变量,我们立即调用了这个临时对象的c_str()函数。这一切都没有问题。之后,完整表达式结束(遇到分号),而这个临时变量没有赋值给某个引用或用于给某个对象初始化,所以这个临时变量被立即销毁。由此对象获得的c_str()函数结果同样被销毁,因此发生段错误。在第二段代码中,这个临时变量用于给sstr对象初始化,我们之后调用的是这个新的被初始化完成的对象的函数,也就是正常的。第三段代码虽然也没有赋值给某个引用或用于给某个对象初始化,但在str.toStdString().c_str()语句结束后,表达式并没有结束,而是继续执行函数调用。直到函数调用返回,才遇到代表表达式结束的分号,此时临时变量才会销毁。而这时候我们已经成功执行了函数代码。所以一切都没有问题。

至此我们明白了这种看似奇怪的现象其实只是一个 C++ 语言的陷阱,甚至与 Qt 没有一点关系。同样类似的陷阱还可能发生在QString::toUtf8()QString::toAscii()之类的函数身上。