之前有朋友问起一个看起来比较“奇怪”的问题,记录如下。因为这个问题豆子也曾经遇到,初学者很有可能感到迷惑。
当时的代码是这样子的:
// 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()
的签名:
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 评论
学习了。我有个疑问,假如有你例子中的foo函数。
有如下代码:
clazz a = foo();
这里发生的对象复制操作有几次?我的理解是:第一次是局部对象c复制到临时对象,然后将临时对象返回后,由于赋值号的存在,又复制了一次给a?所以为了减少不必要的复制,应该写成clazz &a = foo();是这样吗?
是的,写成引用的方式也是可以的。不过 C++ 11 已经有了移动语义,所以使用移动语义重写的赋值运算符性能也是很快的。
不知道作者有没有OrbitsWriter的成品。
目前还没有完成,最近个人方面有些事情一直没有来得及开发,不好意思
能不能把网站的页面设置成宽屏呀,或者自适应也行。我现在看着就是中间一绺,两边剩好多空白。看页面内容得要不停的往下拖动才能看到。
你的屏幕分辨率是多少的?这个主题是自适应的,不知道是什么样子?可以帮忙截下屏发送到邮箱,devbean#outlook.com,多谢关注!
豆子,你好。我遇到了同样的问题,有一个疑问,第一段代码通常在什么情况下可能会有问题呢?
我遇到的问题是同样的第一段代码的写法,在windows系统(msvc2010)、中标麒麟系统(gcc 4.9)下都没有问题,但是在centOs(gcc 8.3.1)下有问题。