繁体   English   中英

当对象被删除时,引用会发生什么?

[英]What happens to a reference when the object is deleted?

我做了一些实验来尝试理解 C++ 中的引用:

#include <iostream>
#include <vector>
#include <set>

struct Description {
  int a = 765;
};

class Resource {
public:
  Resource(const Description &description) : mDescription(description) {}

  const Description &mDescription;
};

void print_set(const std::set<Resource *> &resources) {
    for (auto *resource: resources) {
        std::cout << resource->mDescription.a << "\n";
    }
}

int main() {
  std::vector<Description> descriptions;
  std::set<Resource *> resources;

  descriptions.push_back({ 10 });
  resources.insert(new Resource(descriptions.at(0)));

  // Same as description (prints 10)
  print_set(resources);

  // Same as description (prints 20)
  descriptions.at(0).a = 20;
  print_set(resources);

  // Why? (prints 20)
  descriptions.clear();
  print_set(resources);

  // Object is written to the same address (prints 50)
  descriptions.push_back({ 50 });
  print_set(resources);

  // Create new array
  descriptions.reserve(100);

  // Invalid address
  print_set(resources);

  for (auto *res : resources) {
      delete res;
  }
  
  return 0;
}

https://godbolt.org/z/TYqaY6Tz8

我不明白这里发生了什么。 我从C++ FAQ中找到了这段摘录:

重要提示:即使引用通常使用底层汇编语言中的地址来实现,但请不要将引用视为指向对象的有趣指针。 引用是对象,只是具有另一个名称。 它既不是指向对象的指针,也不是对象的副本。 它是对象。 没有 C++ 语法可以让您对引用本身与它所引用的对象分开进行操作。

这给我带来了一些问题。 那么,如果引用是对象本身并且我在同一内存地址中创建了一个新对象,这是否意味着引用“成为”新对象? 在上面的例子中,向量是线性数组; 因此,只要数组指向相同的内存范围,对象就会有效。 然而,当使用其他数据集(例如集合、映射、链表)时,这变得更加棘手,因为每个“节点”通常指向内存的不同部分。

如果原始对象被破坏,我应该将引用视为未定义吗? 如果是,除了跟踪引用的自定义机制之外,是否有其他方法可以识别引用已被破坏?

注意:使用 GCC、LLVM 和 MSVC 对此进行了测试

该注释具有误导性,将引用视为指针的语法糖可以作为一种心理模型。 在指针可能悬空的所有方式中,引用也会悬空。 访问悬空指针/引用是未定义的行为 (UB)。

int* p = new int{42};
int& i = *p;
delete p;

void f(int);
f(*p); // UB
f(i);  // UB, with the exact same reason

这也扩展到标准容器及其关于指针/引用失效的规则。 在您的示例中发生任何令人惊讶的行为的原因仅仅是 UB。

我对自己解释的方式是:

指针就像你手上的手指。 它可以指向内存块,将它们视为键盘。 所以指针从字面上指向一个持有某物或做某事的键盘。

参考是某事的昵称。 例如,您的名字可能是 Michael Johnson,但人们可能会称您为 Mike、MJ、Mikeson 等。每当您听到您的昵称时,就会有人称您为同一个事物 - 您。 如果您对自己做某事,参考也会显示更改。 如果您指向其他东西,它不会影响您之前指向的内容(除非您正在做一些奇怪的事情),而是指向新的东西。 话虽这么说,就像上面接受的答案一样,如果你用手指和昵称做了一些奇怪的事情,你会看到奇怪的事情发生。

引用可能是 C++ 最重要的特性,它对初学者的编码至关重要。 今天很多学校都是从 MATLAB 开始的,当你想认真做事时,它的速度非常慢。 原因之一是 MATLAB 中缺少控制引用(是的,它有它们,创建一个类并从句柄派生 - 谷歌它),就像在 C++ 中一样。

看看这两个函数:

double fun1(std::valarray<double> &array) 
{ 
    return array.max();
}
double fun2(std::valarray<double> array)
{
     return array.max();
}

这两个简单的功能有很大的不同。 当您有一些 STL 数组并使用 fun1 时,函数将期望该数组的昵称,并将直接处理它而不制作副本。 另一方面, fun2 将获取输入数组,创建其副本并处理该副本。

自然,在 C++ 中创建函数来处理输入时使用引用效率更高。 话虽如此,您必须确保不要以任何方式更改您的输入,因为这会影响您生成它的另一段代码中的原始输入数组 - 您正在处理相同的事情,只是以不同的方式调用。 这使得引用对于有点争议的编码很有用,称为副作用。 在 C++ 中,如果不创建自定义数据类型,则无法直接创建具有多个输出的函数。 一种解决方法是这样的示例中的副作用:

#include <stdio.h>
#include <valarray>
#include <iostream>

double fun3(std::valarray<double> &array, double &min)
{
    min = array.min();
    return array.max();
}
int main()
{
    std::valarray<double> a={1, 2, 3, 4, 5};
    double sideEffectMin;
    double max = fun3(a,sideEffectMin);
    std::cout << "max of array is " << max << " min of array is " << 
    sideEffectMin<<std::endl;
    return 0;
}

所以 fun3 期望引用双精度数据类型。 换句话说,它希望第二个输入是另一个双变量的昵称。 这个函数然后去改变参考,这也将改变输入。 名称和昵称都会被函数更改,因为它是同一个“东西”。 在 main 函数中,变量 sideEffectMin 被初始化为 0,但是当 fun3 函数被调用时它会得到一个值。 因此,您从 fun3 获得了 2 个输出。

该示例向您展示了具有副作用的技巧,但也要注意不要更改您的输入,特别是当它们引用其他内容时,除非您知道自己在做什么。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM