【问题标题】:Is return by value always const?按值返回总是 const 吗?
【发布时间】:2012-11-03 08:22:53
【问题描述】:

此代码无法编译:

class C {};

void foo (C& c) {}

C bar() { return C(); }

int main()              
{
  foo(bar());
}               

foo(bar()) 行中的编译错误 (GCC 4.1.2):

'C&' 类型的非常量引用的初始化无效 来自“C”类型的临时文件

由于bar() 返回一个mutable 对象,它应该编译...
为什么C++ 不允许上面的代码?


编辑:我已经在 an answer below 中总结了所有答案中的所有好主意 ;-)

【问题讨论】:

    标签: c++ return-value rvalue-reference temporary-objects pass-by-rvalue-reference


    【解决方案1】:

    这是因为bar返回的值是一个临时值。因为它的存在是临时的,所以不能使用指针或引用。

    但是,如果您存储该临时对象的副本,就像在您的第二次更改中一样,您不再将对临时对象的引用传递给 foo,而是对真实有形对象的引用.在第一种情况下,当您更改为对常量对象的引用时,编译器会确保临时对象保持足够长的时间(根据 C++ 规范)。

    【讨论】:

    • 好吧,您可以将临时对象绑定到右值引用(并随后获取其地址),因此该对象并不比命名变量更“真实”。
    【解决方案2】:

    可修改(左值)引用不绑定到临时值。但是,const-references do 绑定到临时值。与value返回的对象是否为const无关;这只是表达式是否是临时的问题。

    例如,以下有效的:

    struct C { void i_am_non_const() {} };
    
    int main()
    {
        bar().i_am_non_const();
    }
    

    【讨论】:

    • +1 感谢您对我的回答的评论,我正在更新 ;-)
    • @Kerrek 很抱歉尝试修复您的示例。我忘记了返回C的问题中的bar 函数 :)
    【解决方案3】:

    问题不在于bar 的声明,而在于foo 的声明。 foo 采用非const 引用,而临时对象只能绑定到const 引用(然后会延长临时对象的生命周期以匹配它所绑定的引用的生命周期)。

    允许非const 引用绑定到临时没有多大意义。非const 引用意味着它将修改绑定到它的任何对象。修改临时文件没有任何意义,因为它的生命周期是有限的,一旦超出范围,更改就会丢失。

    【讨论】:

      【解决方案4】:

      这里的适用规则是您不能创建对临时对象的非常量引用。如果foo 被声明为foo(const C&),代码就可以了。

      不过,临时对象本身不是 const;您可以在其上调用非常量成员函数,例如,bar().non_const_member_function()

      在 C++11 中,可以编写 foo 来获取右值引用;在这种情况下,通话就可以了:

      void foo(C&&);
      foo(bar());  // okay
      

      【讨论】:

        【解决方案5】:

        这是一种设计选择。这里没有什么本质上是不可能的。只是一种设计选择。

        在 C++11 中,您有第三种选择,它也是 superior 选择:

        void foo(C && c) {} 
        

        也就是说,使用右值引用。

        【讨论】:

          【解决方案6】:

          真正的事实是,获取对临时值的引用是没有意义的。

          通过引用传递对象的重要之处在于它允许您修改其状态。但是,在临时的情况下,就其本质而言,能够对其进行修改并不是特别有帮助,因为您无法在稍后的代码中获得对它的另一个引用以查看更改。

          但是,如果您有 const 引用,情况会有所不同。由于您只能从const 参考中阅读,因此能够在那里使用临时对象是完全有意义的。这就是为什么编译器会为您“破解”它,并为您想要“转换”为 const 引用的临时地址提供更永久的地址。

          因此,规则是您不能获得对临时值的非const 引用。 (这在 C++11 中略有改变,我们有一种新类型的引用来服务于这个确切的目的,但方法应该以特殊的方式处理这些。)

          【讨论】:

          • 除非它确实有意义......就像从字符串文字构造的临时std::string
          • @KerrekSB,修改字符串文字有什么意义?您不应该将字符串文字分配给 char* 变量。 std::strings 怎么样?谁来获得结果?
          • 假设您有一个想要进一步修改字符串然后返回新字符串的函数。它的签名应该是std::string(std::string s),它会修改s并返回它。您希望能够在原始字符串文字上调用它。可修改地绑定到在调用站点构造的临时字符串非常有用(这就是发明移动语义的目的)。
          • @KerrekSB,感觉就像你从第一句话就停止了阅读。
          【解决方案7】:

          它不是 const,而是一个临时的rvalue。因此,它不能绑定到非 const lvalue 引用。

          它可以绑定到 const 或 rvalue 引用,并且可以在其上调用成员函数(const 或非 const):

          class C { void f(); };
          
          void foo_const(C const &);
          void foo_rvalue(C &&);
          
          foo_const( bar() );  // OK
          foo_rvalue( bar() ); // OK
          bar().f();           // OK
          

          【讨论】:

            【解决方案8】:

            谢谢大家的回答:-)
            在这里我收集你的好主意;-)

            回答

            按值返回不是const。比如我们可以调用按值返回的非常量成员函数:

            class C { 
              public:
                int x;
                void set (int n) { x = n; }  // non-const function
            };
            
            C bar()  { return C(); }
            
            int main ()
            {
                bar.set(5); // OK
            }
            

            但是C++ 不允许非常量引用临时对象
            但是 C++11 允许非 const rvalue-references 到临时对象。 ;-)

            说明

            class C {};
            
            void foo (C& c) {}
            
            C bar()  { return C(); }
            //bar() returns a temporary object
            //temporary objects cannot be non-const referenced
            
            int main()
            {
              //foo() wants a mutable reference (i.e. non-const)
              foo( bar() ); // => compilation error     
            }                 
            

            三个修复

            1. 更改foo 声明

                void foo (const C& c) {}
              
            2. 使用另一个对象

                int main()              
                {
                  C c;
                  foo( c = bar() );
                }
              
            3. 使用 C++11 右值引用

                void foo(C && c) {}
              

            此外

            为了确认临时对象是 const,上面的源代码由于同样的原因失败了:

            class C {};
            
            void foo(C& c) {}
            
            int main()                
            {
              foo( C() );
            }            
            

            【讨论】:

            • 对象不是临时的。 是。并且作为临时值的对象不是常量!
            • 嗨@KerrekSB。你的意思是我的句子 “确认临时对象是 const [...]” 是错误的吗?你建议我写什么?
            • 不确定。关键是C()创建的对象不是const。无法编译最后一段代码的原因是右值不绑定到非 const 左值引用,但对象不是 const。如果C::f 是非常量成员函数,您可以说C().f()。我真的不介意口语中“对象”和“价值”之间的语言混淆,但我认为在这种情况下,这可能是您最终(错误)结论的根本原因。
            猜你喜欢
            • 1970-01-01
            • 2019-04-28
            • 1970-01-01
            • 2011-02-19
            • 1970-01-01
            • 1970-01-01
            • 2012-02-01
            相关资源
            最近更新 更多