【问题标题】:Won an argument my instructor today regarding this but is it correct?今天我的教练在这方面赢得了争论,但这是正确的吗?
【发布时间】:2015-02-18 01:21:04
【问题描述】:

讨论的是函数中的某些动态分配的变量(除 main 之外)是否可以通过引用返回。

我们应用 this 的地方不同,但为了说明这一点,我将考虑返回一个简单的 int,如下所示:

int& myFunc() {
     unique_ptr<int> p(make_unique<int>(10)); 
     int& r = (*p);    // A reference to the value in heap pointed by p.
     return r;
}

现在,即使指针句柄在超出范围后丢失,我们仍然有另一个句柄(即引用变量 r)来访问动态分配的值为 10 的 int。

我的导师说它可能会出现编译错误,但是当我编译并运行代码时,它运行良好。

那么,这是允许的还是有任何危险?

【问题讨论】:

  • 你没有在那里返回任何引用并且你确实在泄漏内存。
  • 你在返回一个副本然后泄漏内存,所以这确实很危险,但允许。
  • 哇,等等。你是什​​么意思我没有返回任何参考?
  • @Bazooka,除非您返回唯一指针,否则它将在函数末尾超出范围并删除其分配的内存,这意味着访问返回的int 的地址将导致在未定义的行为中。
  • 请停止编辑问题。答案会根据您的编辑而不断变化,因为您会不断改变问题!

标签: c++


【解决方案1】:

您的问题的版本 1 使用了此代码:

int myFunc() {
     int* p = new int(10);
     int& r = (*p);    // A reference to the value in heap pointed by p.
     return r;
}

这是按值返回int,而不是按引用。值被复制,引用丢失,分配的内存泄漏。从技术上讲,这都是合法的,但泄漏内存是不好的(而且您永远无法释放分配的内存)。


您问题的第 2 版使用了以下代码:

int& myFunc() {
     int* p = new int(10);
     int& r = (*p);    // A reference to the value in heap pointed by p.
     return r;
}

这也是合法的。它返回对分配对象的引用。该对象比函数调用的寿命更长,并且引用仍然有效。您将需要通过引用捕获对象并将其删除(不要忘记获取其地址!)以避免内存泄漏,但这样做是有效的。

使用此代码的安全方法如下:

int& ref = myFunc();
std::cout << ref << std::endl;
// Free the memory:
delete (&ref);

您问题的第 3 版使用了以下代码:

int& myFunc() {
     unique_ptr<int> p(make_unique<int>(10)); 
     int& r = (*p);    // A reference to the value in heap pointed by p.
     return r;
}

这是无效的。 unique_ptr p 将在函数调用结束时删除其对象,并且该函数将返回一个无效的悬空引用。这是调用未定义行为的众多方法之一。

【讨论】:

  • @AndyG:“编译器不会抱怨”与“不违法”不同。幸运的是,该标准没有使用这种模棱两可的术语。这确实是 UB 而不是 格式错误的代码。
  • @AndyG:我们的物理现实中不可能存在这样的编译器。编译器所做的工作有 50% 超出标准。这就是它的全部意义所在!为您的计算机弥合 C++ 程序和汇编程序之间的差距。
  • 已更新以更仔细地踩踏 C++ 技术术语的雷区 :)
  • 第三种情况突出了一个新点。今天学到了很多新东西。 :)
  • 好!我很高兴它有用!
【解决方案2】:

编译错误不一定是预期的,但您所做的是无效的,因为如果调用者使用返回的引用,它会导致调用者具有未定义的行为。

大多数现代编译器都可以配置为对返回对局部变量的引用的函数发出警告。但是,没有编译器可以立即执行此操作,因此您需要阅读编译器文档(和/或开发环境的文档)以了解如何操作。

但是,由于您的代码掩盖了它在 unique_pointer 后面所做的事情(加上使用动态内存分配),因此编译器很可能不能(并且,如果可以的话,也可能不会)发出任何诊断信息为您的代码。 unique_pointer 是一个模板类,模板实例化是一个复杂的过程,可能会使编译器在这种情况下更难发出诊断。

最后,你在与导师的争论中赢了也输了。

  • 您已经赢了,因为编译器不需要根据您的代码发出诊断。
  • 从某种意义上说,您已经失去了编译错误并不意味着您的函数是正确或有效的。具体来说,您的代码将导致使用返回的引用的任何调用者具有未定义的行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-22
    • 2012-04-16
    • 1970-01-01
    • 2012-04-01
    • 2016-11-23
    • 2013-09-25
    • 1970-01-01
    相关资源
    最近更新 更多