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

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

7 2.1K

之前有朋友问起一个看起来比较“奇怪”的问题,记录如下。因为这个问题豆子也曾经遇到,初学者很有可能感到迷惑。

当时的代码是这样子的:

// 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()的签名:

std::string QString::toStdString() const;

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

clazz foo() const
{
    clazz c;
    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()之类的函数身上。

7 评论

zhouqian 2014年4月14日 - 13:42

学习了。我有个疑问,假如有你例子中的foo函数。
有如下代码:
clazz a = foo();
这里发生的对象复制操作有几次?我的理解是:第一次是局部对象c复制到临时对象,然后将临时对象返回后,由于赋值号的存在,又复制了一次给a?所以为了减少不必要的复制,应该写成clazz &a = foo();是这样吗?

回复
豆子 2014年4月14日 - 14:09

是的,写成引用的方式也是可以的。不过 C++ 11 已经有了移动语义,所以使用移动语义重写的赋值运算符性能也是很快的。

回复
chonghua 2014年4月17日 - 19:43

不知道作者有没有OrbitsWriter的成品。

回复
豆子 2014年4月18日 - 09:33

目前还没有完成,最近个人方面有些事情一直没有来得及开发,不好意思

回复
Sirit 2014年4月22日 - 22:36

能不能把网站的页面设置成宽屏呀,或者自适应也行。我现在看着就是中间一绺,两边剩好多空白。看页面内容得要不停的往下拖动才能看到。

回复
豆子 2014年4月23日 - 08:56

你的屏幕分辨率是多少的?这个主题是自适应的,不知道是什么样子?可以帮忙截下屏发送到邮箱,devbean#outlook.com,多谢关注!

回复
dongcy 2020年5月11日 - 17:03

豆子,你好。我遇到了同样的问题,有一个疑问,第一段代码通常在什么情况下可能会有问题呢?
我遇到的问题是同样的第一段代码的写法,在windows系统(msvc2010)、中标麒麟系统(gcc 4.9)下都没有问题,但是在centOs(gcc 8.3.1)下有问题。

回复

回复 dongcy 取消回复

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2