条件分配参考

时间:2016-01-14 09:11:37

标签: c++ gcc

对于比我更深入了解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

messagetoPrint似乎与我期望的相同。但是,在另一台机器上,会发生这种情况:

# 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 ++标准,哪些行为是正确的?或者一般来说它是不确定的?

2 个答案:

答案 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)类类型:      
        
    • 如果E1E2具有类类型,并且基础类类型相同或者一个是另一个的基类:E1可以转换为匹配{{1如果E2的类与T2的类相同,或者是T1的类的基类,则T2的cv-qualification与cv-qualification相同,或者比T1的cv资格更高的cv资格。如果应用了转换,则通过从E1复制初始化T2类型的临时值并将该临时值用作转换后的操作数,将T2更改为类型E1的prvalue 。 (字符串文字没有类类型)
    •   
    • 否则(即,如果E1E2具有非类型类型,或者它们都具有类类型但基础类不是相同的或一个是另一个的基类): E1可以转换为匹配E2,如果E1可以隐式转换为E2E2转换为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。