C++;引用-它们只是语法上的糖吗? 是C++的引用,只是句法糖,还是在某些情况下提供了加速?< /P>

C++;引用-它们只是语法上的糖吗? 是C++的引用,只是句法糖,还是在某些情况下提供了加速?< /P>,c++,C++,例如,指针调用无论如何都涉及副本,而引用调用似乎也是如此。潜在的机制似乎是相同的 编辑:在大约六个答案和许多评论之后。我仍然认为参考文献只是合成糖。如果人们可以直接回答“是”或“否”,如果有人可以做一个可接受的答案?编译器不能假定指针为非空;在优化代码时,它必须证明指针是非空的,或者发出一个程序来解释它为空的可能性(在定义良好的上下文中) 类似地,编译器不能假定指针从不更改值。(它也不能假设指针指向有效对象,尽管我很难想象在定义良好的上下文中这一点会起作用) 另一方面,假设引用是作为指针实现的,

例如,指针调用无论如何都涉及副本,而引用调用似乎也是如此。潜在的机制似乎是相同的


编辑:在大约六个答案和许多评论之后。我仍然认为参考文献只是合成糖。如果人们可以直接回答“是”或“否”,如果有人可以做一个可接受的答案?

编译器不能假定指针为非空;在优化代码时,它必须证明指针是非空的,或者发出一个程序来解释它为空的可能性(在定义良好的上下文中)

类似地,编译器不能假定指针从不更改值。(它也不能假设指针指向有效对象,尽管我很难想象在定义良好的上下文中这一点会起作用)


另一方面,假设引用是作为指针实现的,编译器仍然可以假定它是非空的,从不更改它指向的位置,并且指向有效的对象。

引用比指针有更强的保证,因此编译器可以更积极地优化。我最近看到GCC通过函数引用完美地内联了多个嵌套调用,但没有一个通过函数指针内联调用(因为它无法证明指针总是指向同一个函数)


如果引用最终存储在某个地方,它通常会占用与指针相同的空间。这并不是说,它会像指针一样使用:如果编译器知道引用绑定到哪个对象,它可能会很好地将其截断。

假设引用是一个指针:

  • 不能为空
  • 初始化后,无法重新指向其他对象
  • 任何使用它的尝试都会隐式取消引用它:

    int a = 5;
    int &ra = a;
    int *pa = &a;
    
    ra = 6;
    
    (*pa) = 6;
    
  • 如拆解中所示:

        int a = 5;
    00ED534E  mov         dword ptr [a],5  
        int &ra = a;
    00ED5355  lea         eax,[a]  
    00ED5358  mov         dword ptr [ra],eax  
        int *pa = &a;
    00ED535B  lea         eax,[a]  
    00ED535E  mov         dword ptr [pa],eax  
    
        ra = 6;
    00ED5361  mov         eax,dword ptr [ra]  
    00ED5364  mov         dword ptr [eax],6  
    
        (*pa) = 6;
    00ED536A  mov         eax,dword ptr [pa]  
    00ED536D  mov         dword ptr [eax],6  
    
    从编译器的角度来看,对引用的赋值与对取消引用的指针的赋值是相同的。正如您所看到的,它们之间没有区别(我们现在不讨论编译器优化) 但是如上所述,引用不能为null,并且对它们所包含的内容有更强的保证


    就我而言,我更喜欢使用引用,只要我不需要将
    nullptr
    作为有效值、应该重新输入的值或要传递到其中的不同类型的值(例如指向接口类型的指针)。

    过去有效率优势,因为引用更易于编译器优化。然而,现代编译器已经非常擅长于此,不再有任何优势

    与指针相比,引用的一个巨大优势是引用可以引用寄存器中的值,而指针只能指向内存块。以寄存器中的某个内容的地址为例,您将强制编译器将该值放入正常内存位置。这可以在紧密的循环中创造巨大的好处

    然而,现代编译器非常优秀,现在它们可以识别出一个指针,该指针可以是所有意图和目的的引用,并将其视为完全相同的引用。这可能会在调试器中产生相当有趣的结果,在调试器中,您可以有一条语句,例如
    int*p=&x
    ,要求调试器打印
    p
    的值,但却让调试器说“p无法打印”,因为
    x
    实际上在寄存器中,编译器将
    *p
    视为对
    x
    的引用!在这种情况下,
    p


    (但是,如果您尝试在
    p
    上执行指针算术,则会迫使编译器不再优化指针,使其像引用那样运行,并且一切都会变慢)

    引用与指针的不同之处在于,您无法对引用执行某些操作,并且必须定义引用的行为

    您不能获取引用的地址,而只能获取引用的内容。一旦创建引用,就不能修改它

    A
    T&
    和A
    T*const
    (注意,
    const
    适用于指针,而不是指向的指针)相对类似。获取实际
    const
    值的地址并对其进行修改是未定义的行为,修改(它直接使用的任何存储)引用也是未定义的行为

    现在,在实践中,您可以从引用的存储中获得以下信息:

    struct foo {
      int& x;
    };
    
    sizeof(foo)
    几乎肯定等于
    sizeof(int*)
    。但是编译器可以忽略直接访问
    foo
    字节的人实际上可能会更改引用的值的可能性。这允许编译器读取引用“指针”实现一次,然后再也不读取它。如果我们有
    struct foo{int*x;}
    ,编译器必须在每次执行
    *f.x
    时证明指针值没有更改

    如果您有
    struct foo{int*const x;}
    is在其不变性(修改声明为
    const
    is UB的内容)中再次开始执行类似于引用的操作


    我不知道编译器编写者使用的一个技巧是在lambda中压缩引用捕获

    如果lambda通过引用捕获数据,而不是通过指针捕获每个值,那么它只能捕获堆栈帧指针。每个局部变量的偏移量都是堆栈帧指针之外的编译时常量

    异常是引用捕获的引用,即使引用变量超出范围,缺陷报告到C++的引用必须保持有效。所以这些必须由伪指针捕获

    具体示例(如果是玩具):


    void part( std::vector<int>& v, int left, int right ) {
      std::function<bool(int)> op = [&](int y){return y<left && y>right;};
      std::partition( begin(v), end(v), op );
    }
    
    void part( std::vector<int>& v, int left, int right ) {
      int* pleft=&left;
      int* pright=&right;
      std::function<bool(int)> op = [=](int y){return y<*pleft && y>*pright;};
      std::partition( begin(v), end(v), op );
    }
    
    struct big { int data[1<<10]; };
    
    std::array<big, 100> arr;
    
    arr get_arr();
    
    for (auto&& b : get_arr()) {
    }