对于比我更深入了解C ++标准的人,请详细说明一下吗?
这是我的示例程序
#include <string>
#include <iostream>
int main(int argc, char* argv[]) {
const std::string message("hello world");
std::cout << std::hex << (void*)message.c_str() << std::endl;
const std::string& toPrint = (argc > 0) ? message : "";
std::cout << std::hex << (void*)toPrint.c_str() << std::endl;
return 0;
}
在一台机器上执行此操作:
# g++ --version && g++ str_test.cpp && ./a.out
g++ (Debian 4.7.2-5) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0x9851014
0x9851014
message
和toPrint
似乎与我期望的相同。但是,在另一台机器上,会发生这种情况:
# g++ --version && g++ str_test.cpp && ./a.out
g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0x7ffeb9ab4ac0
0x7ffeb9ab4ae0
此处看起来编译器为message
构建了toPrint
的副本以指向。
根据C ++标准,哪些行为是正确的?或者一般来说它是不确定的?
答案 0 :(得分:6)
您对GLIBC写时复制字符串共享感到困惑。将您的测试程序更改为:
#include <string>
#include <iostream>
int main(int argc, char* argv[]) {
const std::string message("hello world");
std::cout << std::hex << (void*)&message << std::endl;
const std::string& toPrint = (argc > 0) ? message : "";
std::cout << std::hex << (void*)&toPrint << std::endl;
return 0;
}
(换句话说,打印字符串对象的地址,而不是包含文本的地址),两个平台都将返回不同的地址。
最新标准禁止写入拷贝(虽然我不明白究竟如何)。在此之前它是合法的,但不是强制性的。 (目前的想法是'小字符串优化'确实比牛更好 - 特别是在多线程世界中。)
答案 1 :(得分:4)
Martin Bonner解释了为什么即使对于字符串的副本,地址也可能相同。
为了解释为什么message and toPrint seem to refer to the same instance as I would expect.
被误导,我将引用标准。
让我们首先探讨需要转换的内容(我想这不是问题,只是为了完整性)。忽略第一个否则。它指的是void
类型表达式的情况。
[expr.cond] / 3否则,如果第二个和第三个操作数具有不同的类型,并且具有(可能是cv限定的)类类型,或者两者都是相同值类别的glvalues和除cv之外的相同类型 - 资格化,尝试将每个操作数转换为另一个操作数的类型。确定类型
E1
的操作数表达式T1
是否可以转换为匹配类型E2
的操作数表达式T2
的过程定义如下:
- 如果
E2
是左值,则E1
可以转换为匹配E2
,如果E1
可以隐式转换为“T2
的左值引用” “,受限于在转换中,引用必须直接绑定到左值。 (无法将std::string
类型的左值引用绑定到strig文字)- 如果
E2
是xvalue:E1
可以转换为匹配E2
,如果E1
可以隐式转换为“T2
的右值引用” “,受限于引用必须直接绑定。 (此处没有x值)- 如果
E2
是右值,或者上述两个转换都不能完成且至少有一个操作数具有(可能是cv-qualified)类类型:
- 如果
E1
和E2
具有类类型,并且基础类类型相同或者一个是另一个的基类:E1
可以转换为匹配{{1如果E2
的类与T2
的类相同,或者是T1
的类的基类,则T2
的cv-qualification与cv-qualification相同,或者比T1
的cv资格更高的cv资格。如果应用了转换,则通过从E1
复制初始化T2
类型的临时值并将该临时值用作转换后的操作数,将T2
更改为类型E1
的prvalue 。 (字符串文字没有类类型)- 否则(即,如果
E1
或E2
具有非类型类型,或者它们都具有类类型但基础类不是相同的或一个是另一个的基类):E1
可以转换为匹配E2
,如果E1
可以隐式转换为E2
将E2
转换为prvalue时所具有的类型(或E2
如果std::string
是prvalue,它有它的类型。 (适用)
最后一个子弹涵盖了这个案例。字符串文字具有非类型类型,可以转换为匹配const std::string
prvalue。
现在,我们来探讨转换如何影响结果。
4如果第二个和第三个操作数是相同值类别的glvalues且具有相同类型(它们不是),则结果 属于该类型和值类别,如果第二个或第三个操作数是位字段,则它是位字段,或者如果 两者都是比特字段。
5否则,结果是prvalue。如果第二个和第三个操作数不具有相同的类型,并且具有(可能是cv限定的)类类型,则使用重载决策来确定要应用于操作数的转换(如果有)(13.3.1.2,13.6) 。如果重载决策失败,则程序格式错误。否则,应用如此确定的转换,并使用转换的操作数代替本节其余部分的原始操作数。
所以,结果是一个prvalue!这不是左值参考。你怎么从左值得到一个prvalue?
6对第二个和第三个操作数执行左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换。在这些转换之后,以下之一应该成立:
- 第二个和第三个操作数具有相同的类型;结果是那种类型。如果操作数有 类类型(它们在转换后执行),结果是结果类型的 prvalue temporary ,从任一类型进行复制初始化 第二个操作数或第三个操作数取决于第一个操作数的值。
因此,我们知道结果将从操作数表达式进行复制初始化。即使我们分配了一个引用,并且条件的操作数是对同一类型的左值引用,引用将绑定到临时,从操作数复制。
如果您使用了另一个左值引用{{1}}作为第三个操作数,那么您只需将该值分配给左值,而不是临时的prvalue。