给出了使用C ++返回对象的各种方法,每个返回语句的潜在问题是什么

时间:2016-09-14 13:09:51

标签: c++ c++11 return return-value-optimization

下面是具有各种return语句的代码,并且所有代码都运行良好。 编译器会抛出fun_ret_obj1

的警告
  

Test.cpp:在函数'myClass& fun_ret_obj1()”:   Test.cpp:45:警告:引用局部变量'myObj'返回

但输出似乎仍然很好。是机会吗? 以下任何退货声明是否有任何捕获? 解释会非常有用,谢谢

#include <iostream>
 using namespace std;


 class myClass {
 public:
 int a ;
 myClass()
 {
   a = 10;
 }
 };
 myClass& fun_ret_add()
 {
    myClass *ptr = new myClass();
    return *ptr;
 }

 myClass* fun_ret_ptr()
 {
     myClass *ptr = new myClass();
     return ptr ;
 }

 myClass fun_ret_obj()
 {
     myClass myObj;
     return myObj;
 }

 myClass& fun_ret_obj1()
 {
     myClass myObj;
     return myObj;
 }


 int main()
 {
     myClass obj,obj1;
     std::cout <<"In Main \n";

     myClass *a = fun_ret_ptr();
     std::cout<<a->a<<"\n";

     myClass &b = fun_ret_add();
     std::cout<<b.a<<"\n";

     myClass c = fun_ret_obj();
     std::cout<<c.a<<"\n";

     myClass d = fun_ret_obj1();
     std::cout<<d.a<<"\n";

 }

5 个答案:

答案 0 :(得分:13)

第一个是内存泄漏:

myClass& fun_ret_add()
 {
    myClass *ptr = new myClass();
    return *ptr;
 }

第二个返回一个原始指针(evil - 返回一个std :: unique_ptr)

 myClass* fun_ret_ptr()
 {
     myClass *ptr = new myClass();
     return ptr ;
 }

第三个是完美的 - 返回一个副本,几乎总是被删除。 在c ++ 17中,保证被省略。这是有效和安全的。

 myClass fun_ret_obj()
 {
     myClass myObj;
     return myObj;
 }

更新

在c ++ 17中,您可以保证以这种方式省略副本:

 myClass fun_ret_obj()
 {
     return myClass{};
 }

更新结束

第四个是未定义的行为。返回对不存在的对象的引用。永远不要这样做。

 myClass& fun_ret_obj1()
 {
     myClass myObj;
     return myObj;
 }

关于内存泄漏

在第一个示例中,如果他/她知道myClass已经分配了new,则调用者可以释放内存:

auto& x = fun_ret_add();    // a
...
delete std::addressof(x);   // b

这需要:

  1. 调用者知道 fun_ret_add()是以新的方式实现的。
  2. fun_ret_add()的实现永远不会改变
  3. (a)和(b)
  4. 之间没有例外

    第二个例子是类似的。在这种情况下,至少有一个提示需要删除该对象,但调用者必须知道该对象已被new分配,并且他必须防止异常

    与此形成对比:

    std::unique_ptr<myClass> fun_ret_ptr()
    {
        return std::make_unique<myClass>();
        // or
        return { new myClass() };
        // or
        return std::unique_ptr<myClass>(new myClass());
    }
    

    现在调用者会收到一个智能指针。如果调用者什么也不做,只是使用这个指针,当指针超出范围时,myClass对象将被正确删除,并且所有内存都将被回收。

答案 1 :(得分:2)

 myClass& fun_ret_obj1()
 {
     myClass myObj;
     return myObj;
 }

这会在堆栈myObj上创建一个局部变量。并返回对该对象的引用。然后,由于它的范围,对象被销毁。调用者看到引用的那一刻它引用了一个已经被破坏的堆栈对象,使用它是未定义的行为。因此,您的编译器会向您发出警告。

答案 2 :(得分:2)

确定一些解释:

 myClass fun_ret_obj()
 {
     myClass myObj;
     return myObj;
 }

这个只是调用一个拷贝构造函数。这里没什么特别的。

 myClass* fun_ret_ptr()
 {
     myClass *ptr = new myClass();
     return ptr ;
 }

这个返回指向堆分配对象的指针。在您手动删除它之前,永远将被删除。但回归是安全的。

 myClass& fun_ret_add()
 {
    myClass *ptr = new myClass();
    return *ptr;
 } 

这个将返回对值的引用。虽然这没关系。您不能再访问指针ptr,因此不能手动删除对象。 (好的,你仍然可以删除该对象,但你必须知道这个对象最初是在堆上而不是在堆栈上创建的,不会在其他地方引起任何奇怪的错误。所以这很可能不会在以后被删除)

 myClass& fun_ret_obj1()
 {
     myClass myObj;
     return myObj;
 }

这个严重。当函数超出范围时,将调用析构函数。 (如果在析构函数中将a设置为无效值,您将看到这一点。)

因为计算机是“智能的”并且只是说“这个内存可以在需要时被覆盖”,所以它不会被“删除”(虽然内存位置无效,但析构函数会被调用)。因此,之后直接访问内存会导致看似有效的行为。但这只是意外。当你初始化一些变量或在堆栈上有一些内存分配时,这将被覆盖并且你访问奇怪的内存。这就是为什么这是未定义的行为,编译器在这里警告你。

答案 3 :(得分:1)

偶然的机会。你的编译器不会撒谎。

下面:

myClass& fun_ret_obj1() {
    myClass myObj;
    return myObj;
}

您正在返回对将要销毁的对象的引用 您将面对所谓的未定义行为,它可能有效或无效 在你的情况下:

  

输出似乎很好

当然,这是偶然的。

答案 4 :(得分:0)

  

myClass fun_ret_obj()   {       myClass myObj;       返回myObj;   }

看起来很无辜,但在引擎盖下会创建两个临时对象(每个都调用ctor,传播到所有成员对象的ctors,依此类推......)。然后当返回时将第一个临时副本复制到另一个临时删除它(调用它的dtor和所有成员对象的dtor等等),然后将调用copy ctor(或赋值运算符及其成员对象的那些) ,等等...)对于调用者中的接收对象,然后删除第二个临时(调用所有成员对象的dtor和dtor等)。即使myClass及其所有成员对象都实现了移动语义,这实际上可能是一个非常繁重的操作。最好将引用参数传递给接收对象,并且可能使用POD sentinel(成功/失败)作为函数返回类型,或者使用std :: unique_ptr,如Richard所描述的那样。