代码之家  ›  专栏  ›  技术社区  ›  Edwin Jarvis

C++中的默认传递引用语义

  •  7
  • Edwin Jarvis  · 技术社区  · 16 年前

    编辑:这个问题更多的是关于语言工程,而不是C++本身。我用C++作为一个例子来展示我想要什么,主要是因为我每天都在使用它。我不想知道它在C++上是如何工作的,但是打开了它的讨论。 能够 完成。

    现在不是这样的,我是这样的 希望 这是可以做到的,这肯定会破坏C的可兼容性,但这就是我认为外部“C”的全部意义。

    我的意思是,在您现在声明的每一个函数或方法中,您必须显式地写下,对象将通过引用发送,并在其上前缀引用运算符。我希望每一个非pod类型都能通过引用自动发送,因为我经常使用它,实际上对于每个大小超过32位的对象,这几乎是我的每一个类。

    我们来举例说明一下现在的情况,假设 , C 上课:

    class example {
        public:
            int just_use_a(const a &object);
            int use_and_mess_with_b(b &object);
            void do_nothing_on_c(c object);
    };
    

    现在我希望:

    class example {
        public:
            int just_use_a(const a object);
            int use_and_mess_with_b(b object);
            extern "C" void do_nothing_on_c(c object);
    };
    

    现在,不要做任何事情,因为它的行为和今天一样。

    至少对我来说这很有趣,感觉更清楚,如果你 知道 每一个非pod参数都是引用的,我相信如果必须显式地声明它,那么错误是相同的。

    从另一个角度来看,对于这个变化,来自C的人来说,引用操作符似乎是一种获取变量的方法。 地址 这就是我得到指针的方式。我的意思是,它是同一个操作符,但是在不同的上下文中有不同的语义,这对你来说不是也有点错吗?

    9 回复  |  直到 12 年前
        1
  •  15
  •   Community Bayu Bramantya    7 年前

    我猜你错过了C++和C++语义的要点。你错过了事实 C++在传递(几乎)所有的值时是正确的,因为它总是在C中完成。 . 但不仅仅是在C中,我将在下面向您展示…

    C上的参数语义

    在C语言中,所有内容都是按值传递的。”原语“和”pods“通过复制它们的值来传递。在函数中修改它们,原始的将不会被修改。不过,复制一些pods的成本可能是不平凡的。

    使用指针表示法(the*)时,不会通过引用传递。您正在传递地址的副本。这或多或少是相同的,但有一个细微的区别:

    typedef struct { int value ; } P ;
    
    /* p is a pointer to P */
    void doSomethingElse(P * p)
    {
       p->value = 32 ;
       p = malloc(sizeof(P)) ; /* Don't bother with the leak */
       p->value = 45 ;
    }
    
    void doSomething()
    {
       P * p = malloc(sizeof(P)) ;
       p->value = 25 ;
    
       doSomethingElse(p) ;
    
         int i = p->value ;
       /* Value of p ? 25 ? 32 ? 42 ? */
    }
    

    p->值的最终值为32。因为P是通过复制地址的值来传递的。原来的P没有修改(新的P泄漏了)。

    Java和C Sharp的参数语义

    对一些人来说可能是令人惊讶的,但是在Java中,所有的东西都是用价值复制的。上面的C示例将在Java中给出完全相同的结果。这几乎是您想要的,但是您不能像在C中那样轻松地“通过引用/指针”传递原语。

    在C中,他们添加了“ref”关键字。它或多或少类似于C++中的引用。关键是,在C上,您必须在函数声明和每个调用上都提到它。我想这又不是你想要的。

    C++中的参数语义

    在C++中,几乎所有东西都是通过复制值传递的。当你只使用符号的类型时,你就是在复制符号(就像在C语言中一样)。这就是为什么在使用*时,要传递符号地址的副本。

    但是,当您使用&时,假设您正在传递实际对象(无论是结构、int、指针还是其他对象):引用。

    很容易把它误认为语法糖(即,在幕后,它就像一个指针,生成的代码与指针使用的代码相同)。但是…

    事实上,这个参考不仅仅是合成糖。

    • 与指针不同,它授权像在堆栈上一样操作对象。
    • unline指针,当与const关键字关联时,它授权从一个类型隐式提升到另一个类型(主要通过构造函数)。
    • 与指针不同,符号不应为空/无效。
    • 与“通过复制”不同,您不会花费无用的时间来复制对象。
    • 与“by copy”不同,您可以将其用作[out]参数。
    • 与“复制”不同,您可以在C++中使用全范围的OOP(即,将一个完整对象传递给等待接口的函数)。

    所以,参考文献是两全其美的。

    让我们看看C示例,但是在DoMothTurnFor函数上有C++的变化:

    struct P { int value ; } ;
    
    // p is a reference to a pointer to P
    void doSomethingElse(P * & p)
    {
       p->value = 32 ;
       p = (P *) malloc(sizeof(P)) ; // Don't bother with the leak
       p->value = 45 ;
    }
    
    void doSomething()
    {
       P * p = (P *) malloc(sizeof(P)) ;
       p->value = 25 ;
    
       doSomethingElse(p) ;
    
         int i = p->value ;
       // Value of p ? 25 ? 32 ? 42 ?
    }
    

    结果是42,旧的P被泄漏了,被新的P替换了。因为,与C代码不同,我们不会传递指针的副本,而是传递对指针的引用,即指针本身。

    当使用C++时,上面的例子必须是清晰的。如果不是,那你就错过了什么。

    结论

    C++是通过拷贝/值传递的,因为它是一切工作的方式,无论是在C、C还是Java(甚至在JavaScript……)-…P)。和C++一样,C++有一个引用操作符/关键字, 作为奖金 .

    现在,据我所知,你也许在做我称之为半开玩笑的事。 C+ 也就是说,C具有一些有限的C++特性。

    也许你的解决方案是使用Type Debug(它会激怒你的C++同事,但是看到代码被无用的Type……污染),但是这样做只会混淆你在那里真正丢失C++的事实。

    正如在另一篇文章中所说的,你应该改变你的思维方式,从C开发(无论什么)到C++开发,或者你应该移动到另一种语言。但是不要用C++特性来编程C方式,因为有意识地忽略/混淆你使用的成语的力量,你会产生次优的代码。

    注意:除了原语之外,不要通过复制传递任何内容。你会从你的OO能力去掉你的功能,在C++中,这不是你想要的。

    编辑

    问题是 有点 修改(见) https://stackoverflow.com/revisions/146271/list )我让我的原始答案,并回答下面的新问题。

    在C++上,你如何看待默认通过引用语义? 如您所说,它将破坏兼容性,并且对于原语(即内置类型,仍将通过复制传递)和结构/对象(将作为引用传递)具有不同的传递方式。您必须添加另一个运算符来表示“传递值”(外部的“c”非常糟糕,已经用于其他非常不同的内容)。不,我真的很喜欢今天C++中的方式。

    […]引用运算符在我看来是获取变量地址的一种方法,这是我用于获取指针的方法。我的意思是,它是同一个操作符,但是在不同的上下文中有不同的语义,这对你来说不是也有点错吗? 当和C++流一起使用时,操作符和gt;& gt;改变了它的语义。然后,可以使用operator+=替换strcat。我想,之所以使用运算符是因为它的含义是“指针的对立面”,而且因为它们还不想使用另一个符号(ASCII是有限的,作用域运算符::以及指针->表示其他符号很少可用)。但是现在,如果你感到困扰,那么& & & &将真的让你神经紧张,因为他们在C++0X(一种超级引用……)中添加了一元和(一)。我还没自己消化…

        2
  •  10
  •   Andy Brice    16 年前

    对我来说,完全改变一段代码含义的编译器选项听起来是一个非常糟糕的主意。要么使用C++语法,要么找到不同的语言。

        3
  •  2
  •   aib    16 年前

    我不想再通过使每个(非限定)参数都成为引用来滥用引用了。

    添加到C++中的主要原因是 to support operator overloading ;如果您想要“通过引用”语义,C有一个非常合理的方法来实现它:指针。

    使用指针可以清楚地表明您更改被指向对象的值的意图,并且只通过查看函数调用就可以看到这一点,您不必查看函数声明来查看它是否在使用引用。

    此外,参见

    我想改变这个论点, 我应该用指针还是应该用 参考文献? 我不知道一个坚强的 合理的理由。如果通过``不是 对象“”(例如空指针)是 可接受,使用指针 感觉。我的个人风格是使用 当我想修改 对象,因为在某些上下文中 更容易发现 可以修改。

    from the same FAQ .

        4
  •  1
  •   Ryan    16 年前

    是的,我认为这是一个相当混乱的超载。

    这就是微软对这种情况的看法:

    不要将引用声明与使用运算符地址混淆。如果&identifier前面是类型(如int或char),则将该标识符声明为对该类型的引用。如果标识符前面没有类型,则使用的是运算符地址。

    我在C或C++上不是很好,但是我得到了更大的头痛,整理了两种语言中的*和ANP的不同用法,而不是汇编程序中的编码。

        5
  •  1
  •   Pat Notz    16 年前

    最好的建议是养成思考自己真正想要发生什么的习惯。当您没有复制构造函数(或者不想使用它)并且大型对象更便宜时,通过引用传递是很好的。然而,参数的突变是在类外感觉到的。你可以顺便过去 const 参考——那么没有突变,但是你不能进行局部修改。为廉价对象传递const-by值,这些对象在函数中应该是只读的,并且在需要可以进行本地修改的副本时传递非const-by值。

    每一个排列(按值/参照和常量/非常量)都有重要的差异,这些差异肯定是不等价的。

        6
  •  1
  •   Michael Labbé    16 年前

    当通过值传递时,您正在将数据复制到堆栈。如果为要传递的结构或类定义了operator=我所知道的编译器指令中没有一个会冲走隐含语言混淆的老生常谈,而这种语言混淆是被提议的更改所固有的。

    一个常见的最佳实践是通过常量引用传递值,而不仅仅是通过引用传递值。这样可以确保调用函数中的值不能更改。这是const-correct代码基的一个元素。

    完全正确的代码库更进一步,在原型的末尾添加了常量。考虑:

    void Foo::PrintStats( void ) const {
       /* Cannot modify Foo member variables */
    }
    
    void Foo::ChangeStats( void ) {
       /* Can modify foo member variables */
    }
    

    如果要将一个foo对象传入一个以const为前缀的函数,则可以调用printstats()。编译器在调用changeStats()时出错。

    void ManipulateFoo( const Foo &foo )
    {
        foo.PrintStats();  // Works
        foo.ChangeStats(); // Oops; compile error
    }
    
        7
  •  1
  •   Jason Plank Steve Leung    13 年前

    老实说,C++中的整个值传递/引用概念是误导性的。 一切 是传递值。您有三个案例:

    1. 传递变量的本地副本的位置

      void myFunct(int cantChangeMyValue)
      
    2. 将指针的本地副本传递到变量

      void myFunct(int* cantChangeMyAddress) {
          *cantChangeMyAddress = 10;
      }
      
    3. 在这里传递一个“引用”,但通过编译器的魔力,就好像每次传递一个指针并简单地取消引用。

      void myFunct(int & hereBeMagic) {
          hereBeMagic = 10; // same as 2, without the dereference
      }
      

    我个人认为,记住一切都是有价值的。在某些情况下,该值可能是一个地址,它允许您更改函数之外的内容。

    你的建议不允许程序员做1。我个人认为取消这个选择是个坏主意。C/C++的一个主要优点是有细粒度的内存管理。让一切通过引用仅仅是试图使C++更像Java。

        8
  •  0
  •   ugasoft    16 年前

    有些事情不清楚。当你说:

    内部b(b¶m);

    你打算用第二个“B”做什么?你忘了介绍一种类型吗?你忘了写首字母“b”的不同吗?你不觉得写这样的东西很清楚吗?

    class B{/*something...*/};
    int b(B& param);
    

    从现在起,我想你是指我写的东西。

    现在,您的问题是:“您不认为编译器会将非pod的每个pass-by值视为pass-by-ref会更好吗?”. 第一个问题是它会破坏你的合同。我想你的意思是通过常量引用,而不仅仅是引用。

    现在您的问题简化为:“您知道是否有一些编译器指令可以按值优化函数调用吗?”

    现在的答案是“我不知道”。

        9
  •  0
  •   Lorenzo Boccaccia    16 年前

    我认为,如果你开始混合所有可用的参数,以及它们的常量变化,C++会变得非常混乱。

    它很快就失去了控制,无法跟踪所有复制构造函数调用,所有取消引用都过载了,等等。