给定了在C++中返回对象的各种方法,这些返回语句中的每一个都有哪些潜在的问题

Given are the Various Ways to return an object in C++, what are the potential Issues with each of these return statements

本文关键字:返回 每一个 语句 问题 C++ 对象 方法      更新时间:2023-10-16

下面的代码有各种各样的返回语句,它们都运行得很好。编译器抛出fun_ret_obj1 的警告

Test.cpp:在函数'myClass&fun_ret_obj1((':Test.cpp:45:警告:对本地变量"myObj"的引用返回

但产出似乎仍然不错。是偶然吗?下面的任何一个return语句都有问题吗
解释会很有帮助,谢谢

#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";
 }

第一个是内存泄漏:

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

第二个返回一个原始指针(邪恶-返回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((是用new实现的
  2. fun_ret_add((的实现永远不会改变
  3. (a(和(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对象将被正确删除,并且所有内存都将被回收。

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

这将在堆栈上创建一个局部变量myObj。并返回对该对象的引用。然后,由于对象的作用域,该对象被销毁。调用方看到引用的那一刻,它正在引用一个已被销毁的堆栈对象,使用它是未定义的行为。因此,您的编译器会警告您这一点。

好的一些解释:

 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;
 }

这是关键。函数一旦超出作用域,就会调用析构函数。(如果在析构函数中将设置为无效值,则会看到此情况(。

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

这是偶然的。你的编译器不会说谎。

此处:

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

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

的输出似乎很好

当然,这是偶然的。

myClass函数_ret_obj(({myClass myObj;return myObj;}

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