【问题标题】:Confusion in C++C++中的困惑
【发布时间】:2010-06-26 15:13:55
【问题描述】:

我对 C++ 非常陌生,目前正在学习它。我有几个问题..

  1. void DoSomething(const Foo& foo)void DoSomething(Foo foo) 有什么区别? 如果我们不指定 & 那么 Foo 的实例会按值传递(不是引用)。除了在编译时不检查外,它与在参数中使用 const + & 相同。那么,为什么 const + & 会成为没有 & 和 const 的论点的最佳实践呢?

    在 C# 中,传递对象是“通过引用”,但似乎不是在 C++ 中。

  2. 我正在阅读的书说成员函数通过引用传递隐式参数..

    谁能给我隐式参数的样本并通过引用?我知道如果我们想通过引用传递对象,我们需要使用 & (例如 Foo(Person& p) )但是 C++ 如何通过引用传递对象以获取隐式参数?我读到 C++ 中的隐式参数就像 Contructor(string str) : strMemberVariable(str) {} ...

  3. 数组是唯一在 C++ 中通过引用传递的吗?

  4. 为什么我不能在 Foo 类中使用 Foo fInstance

例子:

class Foo {

public:    
    Foo() { }

    Foo(const Foo& f) : fInstance(f) {   }  

    Foo fInstance;      
};

提前致谢。

【问题讨论】:

    标签: c++ pass-by-reference parameter-passing


    【解决方案1】:

    1 void DoSomething(const Foo& foo) 和void DoSomething(Foo foo) 有什么区别?如果我们不指定 & 那么Foo 的实例将按值传递(而不是引用)。除了在编译时不检查外,它与在参数中使用 const + & 相同。那么,为什么 const + & 会成为没有 & 和 const 的论点的最佳实践呢?

    在 C# 中,传递对象是“通过引用”,但似乎不是在 C++ 中。

    有几个区别,按重要性排序:

    • 如果无法复制对象Foo,则需要通过引用传递
    • 如果对象Foo是基类,你应该通过引用获取它,以便用户可以使用派生类调用你的函数
    • 即使您持有对它的const 引用,实际对象的值也可能会发生变化
    • 效率,复制用户类型可能会很昂贵,但编译器可能足够聪明,可以解决这个问题...

    2 我正在阅读的书说成员函数通过引用传递隐式参数..

    谁能给我隐式参数的样本并通过引用?我知道如果我们想通过引用传递对象,我们需要使用 & (例如 Foo(Person& p) )但是 C++ 如何通过引用传递对象以获取隐式参数?我读到 C++ 中的隐式参数就像 Contructor(string str) : strMemberVariable(str) {} ...

    通过隐式参数你应该理解this,即对象本身。它通过引用有效地传递,因为您可以在成员函数中修改它的状态。

    按照Konrad的说法:注意this本身不是通过引用传递的,this是对对象的引用(指针),而是通过值传递的。您不能随意更改对象的内存地址;)

    3 数组是唯一在 C++ 中通过引用传递的吗?

    他们不是。你会看到数组元素的变化,但数组(结构)不会改变。

    根据FredOverflow的说法,一个插图:

    void fun(int* p, size_t size);
    
    int main(int argc, char* argv[])
    {
      int array[15];
      fun(array, 15);
    }
    

    我们不知道fun 做了什么,它可能会改变array 的一些元素,但不管它的作用是什么,array 将保持一个 15 个整数的数组:内容会改变,结构不会改变。

    因此,要更改array,我们需要另一个声明:

    void changer(int*& array, size_t& size);
    

    这样我们可以同时改变内容和结构(也可以传回新的大小)。当然我们只能用动态分配的数组来调用这个函数。

    4 为什么我不能在 Foo 类中使用 Foo fInstance?

    因为那是无限递归。从编译器的角度考虑它,并尝试猜测Foo 的大小。 Foo 的大小是其属性大小的总和,可能还有一些填充和类型信息。此外,对象大小至少为1,以便可以对其进行寻址。那么,如果Foo 有一个Foo,它的大小是多少:)?

    通常的解决方案是使用智能指针:

    class Foo
    {
    public:
    
    private:
      std::unique_ptr<Foo> mInstance;
    };
    

    因为指针的大小不依赖于指向的对象的大小,所以这里没有递归:)

    【讨论】:

    • "你会看到数组元素的变化,但数组(结构)不会改变。" -> 我不明白你的意思,请详细说明。
    • 很好的答案。我唯一的问题是this 指针:它真的是通过引用传递的吗?我不这么认为——我认为它是按价值传递的。当然,更改是非法的,即使使用const_cast,编译器也会将其视为右值。
    • @Konrad:谈论通过const_cast 更改this 甚至没有意义,因为this 不是const 开头的:) this 是一个指针,指针是标量类型,标量类型没有常量右值。
    • @Konrad:传递指向对象的指针是通过引用传递对象的一种方式。
    • @bk1e:当然可以,但是“引用”在 C++ 中具有非常独特、固定的含义,指针和引用是一回事,传递指针(按值)并按引用传递。
    【解决方案2】:

    由于这里有很多误解和彻头彻尾的错误答案,这是我试图纠正这个问题的尝试:

    void DoSomething(const Foo&amp; foo)void DoSomething(Foo foo)?有什么区别

    正如其他人所说,第二个代码需要一个副本(通常调用Foo的复制构造函数)。

    那么,为什么 const + & 会成为没有 & 和 const 的论点的最佳实践?

    其他人已经回答了一些特殊目的(例如运行时多态性)。这并不能解释为什么它已成为最佳实践。这样做的原因既简单又丑陋:因为它数量级更有效率。想象一下将向量或字符串传递给另一个方法——或者基本上只是任何大数据结构。复制它的成本通常会很大,而且代码中可能会经常调用方法——事实上,方法通常会被非常频繁地调用,否则代码设计得很糟糕。

    另一方面,当您将对象作为const 引用传递时,这是在内部(通常)通过指针实现的。在所有架构上,指针总是可以被有效地复制。

    我正在阅读的书说成员函数通过引用传递隐式参数..

    我认为这本书是错误的。类的成员函数隐式传递了一个指向当前对象的this 指针。但是,这是一个指针,C++ 禁止更改它。没有理由通过引用传递它。

    数组是唯一在 C++ 中通过引用传递的吗?

    C++ 中很少传递数组——它们通常作为指针传递:

    void foo(int[] x) { … }
    

    其实是一样的

    void foo(int* x) { … }
    

    编译器将这两个声明视为相同。当你尝试调用这些方法中的任何一个并将其传递给数组x 时,C++ 会隐式地将数组转换为指向其第一个元素的指针——这称为“衰减”。所以,foo(x) 将变为 foo(&amp;x[0])

    但是,如果数组的大小给定,可以通过引用传递:

    void foo(int (&x)[4]);
    

    但再一次,您明确声明数组是通过引用传递的。

    【讨论】:

    • void foo(int x[4])void foo(int x[])void foo(int *x) 完全相同。大小被简单地忽略。数组永远不会通过引用隐式传递。您应该将最后一部分更改为void foo(int (&amp;x)[4])
    • 我现在喜欢你的回答 :) +1 来自我。
    • 太棒了..非常感谢您的回答..我现在有清楚的理解了...非常感谢..
    【解决方案3】:

    在 C# 中,传递对象是“通过引用”,但似乎不是在 C++ 中。

    不,这是错误的,这是一种常见的误解。在 C#、VB 和 Java 等语言中,变量总是按值传递(异常在 C# 中显式传递为 ref 或在 VB 中以 ByRef 传递)。

    与 C++ 的不同之处在于变量不包含类的对象本身,它们只包含引用。所以传递给方法的不是对象本身,而是它的引用(但that是按值传递的)。

    区别相当重要。如果 C# 使用通过引用传递,以下代码将打印不同的结果:

    void foo(string s) {
        s = "world";
    }
    
    string s = "hello";
    foo(s);
    Console.WriteLine(s); // prints "hello"
    

    【讨论】:

    • >> 在 C# 中显式传递为 ref 或在 VB 中作为 ByRef 传递)我不这么认为。在 C# 中,当您传递对象时,按引用是默认的...
    • 对不起.. 我应该提到在 C# 中传递对象是通过 ref (不是字符串或 int 等)。
    • @Michael:“在 C# 中,当你传递对象时,默认是通过引用”-> 不,只是引用是按值传递的。这与通过引用传递不同,您可以在 C# 中使用 ref 关键字来实现。
    • @Michael:string 是 C# 中的一个普通类,没有什么特别之处。您可以使用任何其他类而不是 string 来测试此示例并获得相同的行为。
    【解决方案4】:

    为什么我不能在 Foo 类中使用 Foo fInstance?

    因为从概念上讲,Foo 的对象将需要无限量的空间。从技术上讲,Foo 的定义中的 Foo 类型是不完整的。

    您可能想要的是一个指向 Foo 作为成员的指针。

    【讨论】:

      【解决方案5】:

      void DoSomething(const Foo&amp; foo)void DoSomething(Foo foo) 的区别在于第一个通过引用传递参数,第二个通过值传递。实际区别是:

      1. 效率。按值传递可能需要调用复制构造函数。如果复制构造函数开销很大,按值传递会增加更多开销。
      2. 适用性。按值传递需要公共复制构造函数。如果类不支持复制构造函数,则不能按值传递。
      3. 语义。通过引用传递时,您不知道对象可能被引用了谁。如果底层对象因其他原因发生更改,则引用的值也会更改。

      为了更好地解释 #3,请考虑以下情况:

      std::string global_string;
      
      void foo(const std::string &str)
      {
          if (str.empty())
          {
              global_string = "whatever";
              // is str still empty??
          }
      }
      

      如果 foo 被称为foo(global_string),那么当您更改global_string 时,这也会更改str

      【讨论】:

      • 是的。它是通过引用传递的,但我们将“const”放在那里,所以无论如何它都不会被修改,对吧?为什么写 (const Person& p) 而不仅仅是 (Person p) 是一种好习惯?感谢您的回答 3..
      • @MichaelSync - 通过 const 引用传递更好,因为它更有效并且即使无法复制类型(即我的 #1 和 #2)也可以工作。 const表示该函数不会修改对象,但不包括通过其他方法修改对象的情况。
      • 谢谢。我在帖子中又添加了一个问题。你也可以回答这个问题吗?谢谢。
      • 但是在你的例子中,函数可能会改变str,因为global_string 的别名:)
      • 我要补充一点,通过引用传递允许传递派生类型的对象......这非常有用!
      【解决方案6】:

      一次一个:

      1. doStuff(Foo f) 表示在调用该方法时将在堆栈上创建一个新的Foo 对象 - AKA by-value。调用 doStuff(const Foo &amp;f) 意味着您只是传递了一个新的引用,对象不重复,您只持有对它的引用。这是传递参数的最安全方式,因为它不涉及复制对象的副本。这称为按引用传递,是最接近 Java/C# 行为的方法。

      2. 你说的是哪个隐式参数?

      3. 同样,数组(假设它们是std::arrays)可以通过值、指针或引用传递——没有单一的行为。正如 Konard 所提到的,C 风格的数组(只不过是内存块)不能按值传递。

      【讨论】:

      • 谢谢。是的。但是当我们将“const”放在第一个中时,该对象无法修改,因此它将与“Person p”相同。我不确定为什么使用“const Person& p”而不是“Person p”如果是同一件事?
      • 不代表不能修改对象,只代表不能修改引用。
      • @Yuval 什么?引用总是隐式不变的。 const T&amp; x 表示 x 是对 T 对象的引用,并且编译器会检查您是否没有修改该 T 对象通过引用 x
      • 数组不能在 C++ 中按值传递。
      • @Fred:std::vector 也可以——它只是任何其他类。 ;-) 但我认为这里的 意思 是好的旧 C 样式数组。
      【解决方案7】:

      通过 const 引用而不是按值传递并不是完全被接受的“良好做法”。

      This blog post 解释了原因。

      人们倾向于认为 const 引用更快,但事实是编译器允许在按值传递时优化掉副本,因此按值传递是一个很好的默认值(事实上,标准库通常会这样做. 例如,std::for_each 采用两个迭代器按值和一个函子按值

      使用 const 引用的主要原因是如果对象不能在逻辑上被复制。假设对象代表一个窗口。您不希望第二个窗口出现在屏幕上,因为您将窗口对象传递给另一个函数,隐式创建了一个副本。

      许多对象代表不能或不应该复制的东西。这些通常有一个私有复制构造函数,并且必须通过引用或 const 引用传递给函数。

      通过引用(const 或其他)传递的另一个原因可能是使用多态对象。假设您有一个基类B 和一个派生类D。您可以安全地将 D 类型的对象作为 const B&amp; 传递,但将其作为 B 类型的对象按值传递可能会引入切片(仅复制 B 子对象,而不是整个 D 对象) .

      所以一个好的做法是按值传递默认,但是通过 const 引用传递当然也有它的位置。两者都使用该语言是有原因的。

      【讨论】:

      • @jalf:让我怀疑。标准库仅按值传递迭代器和函数对象,并在这些对象通常很小的假设下运行。 如果编译器确实可以优化传递值,那么这肯定只在最近的编译器中是正确的,即使在特殊情况下也是如此。我完全不相信即使是现代编译器也会省略参数的副本(而不是返回值)。您所引用的博文也不支持您:它只讨论 NRVO(即返回值)和在需要复制的地方按值传递。
      • 最后但同样重要的是,如果你说的是真的,右值引用实际上是多余的。实际上,它们被引入到语言中正是为了解决这些缺点。
      • @jalf:我不同意。迭代器是特殊的,它们被设计按值传递,函数对象也是如此。如果你看一下 Effective C++,这本书建议通过值传递原语、迭代器和仿函数,几乎所有其他东西都通过引用到 const。您链接的文章仅适用于 C++0x。如果没有移动语义,按值传递所有内容是一个糟糕的建议,恕我直言。
      • @Konrad:现代编译器非常擅长复制省略。诚然,它在旧编译器中不太可靠,但今天,这是一个相当安全的选择。并且博客文章指出,在无论如何都打算复制的情况下,按值传递可能更快,但在任何情况下它通常不慢。当然,不能保证每个副本都会被忽略,但如果您查看编译器输出,情况往往会令人印象深刻。 :) 随意证明我错了。试一试,检查反汇编。
      • @Konrad:不,引入右值引用还有其他一些原因。完美转发就是一个例子。如果没有右值引用,移动语义也无法完整而稳健地实现。复制省略只能为您提供一些方法(并且依赖于编译器进行优化)
      【解决方案8】:
        What is the differences between void DoSomething(const Foo& foo) and
      

      无效DoSomething(Foo foo)?

      实际上没有区别,const 将阻止您更改 'foo' 的内容,而按值传递也不会影响参数的内容,但是就有效性而言, const Foo& foo 更有效,因为它将对象传递给方法时不会创建副本。

      【讨论】:

        【解决方案9】:

        我正在阅读的书说成员函数通过引用传递隐式参数..

        这本书正在讨论传递给类中定义的每个非静态成员函数的隐式指针this。这是因为 C++ 在类中而不是在每个对象中都保存了每个成员函数的副本,因此该方法应该知道它应该处理该类的哪个对象。

        class FOO{
         int x;
         void doSomthing(int x);
        }
        
        void FOO::doSomething(int x){
          x = x;
        }
        

        会被编译成类似的东西

        void FOO::doSomething(FOO* this, int x){
          this->x = x;
        }
        

        由于静态函数是类函数而不是对象函数,因此它们不需要创建对象即可被调用,因此它们不应访问类的非静态字段,因此不会需要一个指向对象的 this 指针。

        【讨论】:

        • this-&gt;,你的例子似乎不正确,它应该编译成this-&gt;x = this-&gt;x,这是非常没用的。这个技巧只在初始化列表中有效。
        • @Mathieu: 不,x 指的是局部变量(参数——内部作用域隐藏了外部作用域),而this-&gt;x 指的是成员。
        • @Matthieu:Konard 所说的就是我的意思。但是您对 -> 运算符而不是 .操作员。我的错误:)
        【解决方案10】:

        void 和 void 有什么区别 DoSomething(const Foo& foo) 和 void DoSomething(Foo foo)?

        广义地说,后者将深度复制传递的参数(换句话说,它会复制原始 Foo 对象)。前者将对传递的参数进行浅拷贝(将其地址复制到不可变的 const 引用,而不是复制实际的 Foo 对象)。

        这两个版本都可以访问正在传递的 Foo 对象的成员。他们都不会修改调用者中的 Foo 对象。如果该函数需要深拷贝,基本区别在于前者效率更高,因为它避免了深拷贝的需要。

        谁能给我隐式参数的样本并通过引用?我知道如果我们想通过引用传递对象,我们需要使用 & (例如 Foo(Person& p) )但是 C++ 如何通过引用传递对象以获取隐式参数?我读到 C++ 中的隐式参数就像 Contructor(string str) : strMemberVariable(str) {} ...

        在参数化的一元构造函数(构造函数接受一个参数)的上下文中,它们可以是隐式(默认)或显式的。

        class Foo
        {
            Foo(int x) {...}
        };
        

        这是隐含的。它允许我们编写如下代码:

        Foo foo = 123;
        
        void f(const Foo& x);
        f(123);
        

        虽然这是明确的:

        class Foo
        {
            explicit Foo(int x) {...}
        };
        

        ... 而不是之前的代码。之前的代码必须相应地修改:

        Foo foo(123);
        
        void f(const Foo& x);
        f(Foo(123) );
        

        通常将此类构造函数显式化是一个好习惯,但复制构造函数除外,因为它涉及的内容比较多,所以我不会在这里讨论。

        数组是唯一经过的吗 C++ 中的引用?

        我不确定这里问的是什么,但如果你的意思是这样,数组就不能按值传递。我们只能传递指向数组的引用/指针:

        // takes an array of 10 integers
        void fn(int(&some_array)[10]);
        
        // takes a pointer to an int array
        void fn(int* some_array);
        
        // takes a pointer to an int array (the 10 
        // literal constant is ignored) and this function
        // can likewise take any pointer to int
        void fn(int some_array[10]);
        

        为什么我不能在 Foo 中使用 Foo fInstance 上课?

        这是无限递归的。 Foo 存储 fInstance,fInstance 存储另一个 fInstance,依此类推。没有什么可以阻止递归,所以你只需要存储对象的对象存储存储对象的对象等等,直到内存不足。因此编译器会检测到该条件并禁止它,因为它不会产生合法的运行时行为。也无法确定 Foo 的大小 - 这将是一个无限值。

        【讨论】:

          【解决方案11】:
          void DoSomething(Foo foo)
          

          实际上传递了一个foo的副本,并且

          void DoSomething(Foo& foo)
          

          传递对 foo 的引用,因此如果您在函数中修改 foo,您将修改原始 foo。我希望这是有道理的。

          对于数组,数组实际上是指向数组开头的指针,并且该指针被传递(不复制整个数组)。

          array[5] = 0;//is the same as :
          *(array+5) = 0; //this
          

          【讨论】:

          • 您答案的最后两行不正确。你假设sizeof(array[0])==1
          • 不是真的,指针有一个类型,并且会增加 sizeof(that_type)。 (如果我理解正确你的意思)
          • @YuvalA - 你不正确。 arr[x]*(arr + x) 在 C++ 中是语义等价的。指针算术会考虑指针的类型并做正确的事情。
          • 在 C++ 中绝对没有办法按值传递引用。
          • "传递一个对 foo 的常量引用,所以如果你在你的函数中修改 foo" -> 但是你不能修改foo!您明确标记const 的不是引用,而是对象上的视图。引用是总是隐式不变的。 C++ 中不存在可变引用。
          【解决方案12】:

          void DoSomething(const Foo& foo) 和 void DoSomething(Foo foo) 有什么区别

          DoSomething(Foo foo) 如果 Foo 是原始数据类型,则按值传递对象 foo,但如果 Foo 是用户定义的数据类型,则按引用传递。但是在第二种情况下,如果您更改 foo,它会反射回原始对象,这通常是不可取的。这由 DoSomething(const Foo& foo) 处理,它通过引用传递 foo(从而节省了通过值传递的额外内存成本),并且仍然没有将 foo 上的写访问权限授予 DoSomething 函数。因此,这是一种最佳做法。

          谁能给我样品 隐式参数和引用?

          成员函数中隐式参数的一个例子是对父对象的引用,即。 this 从未在函数定义中提及,但始终可用。

          数组是唯一经过的吗 C++ 中的引用?

          不,所有用户定义的对象都是通过引用传递的。

          【讨论】:

          • 好的。 this -> membervariable 的意思是“隐式参数”,不是吗?我现在很清楚了。谢谢。
          • "所有用户定义的对象都通过引用传递" -> 完全错误。您是否将 C++ 与 C# 或 Java 混淆了?
          猜你喜欢
          • 2019-02-01
          • 2020-07-18
          • 2021-12-15
          • 2018-01-11
          • 1970-01-01
          • 1970-01-01
          • 2012-01-05
          • 2013-07-23
          • 1970-01-01
          相关资源
          最近更新 更多