引用向量中的对象(现代 C++)

Referencing objects in vector (Modern C++)

如果这很简单,我很抱歉,但我已经 15 年没有玩过 C++ 了。 考虑这个简单的例子: 向量包含 A 类型的对象。 class B 的对象必须引用驻留在向量中的 A 对象。 (编辑澄清 - class B 必须有一个引用 A 实例的成员)

在过去,您只需声明一个 A* 并完成它,但今天如何使用智能指针来完成它?我不想在向量中存储共享或唯一指针,因为我不想在整个堆上分配 A 对象。它们必须在向量本身中。

智能指针的主要目的是管理生命周期。
观察者(即非拥有指针)仍然可以是原始指针。

根据您的要求,您有几个选项。

非拥有原始指针A*

在现代 C++ 中使用非拥有原始指针并没有错。如果 B 需要对 A 的可空引用,并且 A 可以保证比 B 更长寿,那么原始指针就完全可以了。引用可能需要为空的原因之一是如果您需要默认构造 B 然后稍后设置引用:

class B {
  A* a_ = nullptr;
public:
  void setA(A& a) { a_ = &a; }
};

int main() {
  std::vector<A> aVec(3);
  B b;
  b.setA(aVec[1]);
}

引用A&

如果引用不需要为空。如果引用在 B 的构造函数中设置并且永远不会更改,那么您可以使用引用 A&:

class B {
  A& a_;
public:
  B(A& a) : a_(a) {}
};

int main() {
  std::vector<A> aVec (3);
  B b(aVec[1]);
}

std::reference_wrapper<A>

使用引用的一个问题是您无法重新设置引用以指向不同的 a,这意味着您不能拥有 setA 成员函数而且您不能有一个赋值运算符 B::operator=(const B&)。您可以只使用一个非拥有的原始指针并强制该原始指针永远不应该为空。但是标准库现在提供了一个名为 std::reference_wrapper 的便利,它不能像引用一样为空,但可以像指针一样重新设置:

class B {
  std::reference_wrapper<A> a_;
public:
  B(A& a) : a_(a) {}
  void setA(A& a) { a_ = a; }
};

int main() {
  std::vector<A> aVec (3);

  B b(aVec[1]);
  B otherB(aVec[2]);

  b = otherB;  // use auto-generated assignment operator
  b.setA(aVec[0]);
}

vector

中元素的索引

一个常见的情况是 Avector 正在增长,因此它可能会重新分配和使引用、指针和迭代器无效。在这种情况下,您可以将索引存储到 vector 中的元素。当 vector 增长时,该索引不会失效,您还可以检查索引是否在向量范围内而不是悬空:

class B {
  std::vector<A>& aVec_;
  int             aIndex_;
public:
  B(std::vector<A>& aVec, int aIndex) : aVec_(aVec), aIndex_(aIndex) {}

  void useA() {
    if (aIndex_ >= 0 && aIndex_ < aVec_.size()) {
      auto& a = aVec_[aIndex_];
      // use a ...
    }
  }
};

int main() {
  std::vector<A> aVec (3);

  B b(aVec, 1);
  b.useA();
}

如果您要在 Avector 中添加和删除,那么这些方法中的 none 会起作用,您可能需要重新考虑您的设计。

显而易见的答案是最简单的,存储对象的索引,而不是地址。