下面是具有各种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";
}
答案 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
这需要:
第二个例子是类似的。在这种情况下,至少有一个提示需要删除该对象,但调用者必须知道该对象已被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所描述的那样。