仅使用标准库创建 "Vector of References" 的标准实践

Standard Practice for Creating a "Vector of References" Using Only the Standard Libraries

我想创建一个对象,将对象放入 vector,并且仍然能够通过仅访问 vector 来修改同一个对象。但是,我理解当一个对象是push_back()到一个vector时,这个对象实际上是复制到vector。因此,访问 vector 中的对象只会访问一个相似但不同的对象。

我是C语言的初学者,所以我知道我可以创建一个指向对象的指针,并制作一个指针向量。例如vector<Object *>。但是,好像pointers are discouraged in C++, and references are preferred. Yet, I cannot make a vector of references.

我只想使用标准库,所以 boost 对我来说是禁区。

我听说过智能指针。但是,看起来好像有多种类型的智能指针。为此目的会不会太过分了?如果智能指针确实是答案,那么我如何确定使用哪一个?

所以我的问题是:创建对象的 references/pointers 向量的标准做法是什么?

换句话说,希望下面的(伪)代码能够工作。

#include <iostream>
#include <cstdlib>
#include <vector>

using namespace std;

class Object 
{
public:
    int field;
};

vector<Object> addToVector(Object &o)
{
    vector<Object> v;
    v.push_back(o);
    v[0].field = 3; // I want this to set o.field to 3.

    return v;
}

int main()
{
    Object one;
    one.field = 1;
    cout << one.field << endl; // 1 as expected

    Object &refone = one;
    refone.field = 2;
    cout << one.field << endl; // 2 as expected

    vector<Object> v = addToVector(one);

    cout << v[0].field << endl; // 3 as expected

    cout << one.field << endl; // I want to get 3 here as well, as opposed to 2.


    return 0;
}

I would like to create an object, put the object into a vector, and still be able to modify the same object by accessing only the vector. However, I understand that when an object is push_back() to a vector, the object is actually copied into the vector. As a result, accessing the object in the vector will merely access a similar, but different object.

我几乎可以肯定这不是您想要的或 "should" 想要的。请原谅我直接打开我的答案,但除非你有很好的理由这样做,否则你可能不想这样做。

为此 - 带有引用的向量 - 要工作,您必须 保证在您持有对它们的引用时引用的对象不会被移动或破坏。如果您将它们放在矢量中,请确保矢量未调整大小。如果您像示例中那样将它们放在堆栈上,那么不要让引用向量或其副本离开该堆栈帧。如果您想将它们存储在某个容器中,请使用 std::list(它是迭代器 - 基本上是指针 - 在插入或删除元素时不会失效)。

您已经注意到您不能拥有 "real" 个引用的向量。因此,原因是引用不可分配。考虑以下代码:

int a = 42;
int b = 21;
int & x = a; // initialisation only way to bind to something
int & y = b;
x = y;
b = 0;

之后,您从 x 获得的值将是 21,因为赋值并没有更改引用(绑定到 b)而是引用的对象 a.但是一个std::vector explicitly requires这个。

您现在可以着手编写一个围绕指针的包装器,例如...

template<typename T>
struct my_ref {
  T * target;
  // don't let one construct a my_ref without valid object to reference to
  my_ref(T & t) : target(&t) {}
  // implicit conversion into an real reference
  operator T &(void) {
    return *target;
  }
  // default assignment works as expected with pointers
  my_ref & operator=(my_ref const &) = default;
  // a moved from reference doesn't make sense, it would be invalid
  my_ref & operator=(my_ref &&) = delete;
  my_ref(my_ref &&) = delete;
  // ...
};

...但这毫无意义,因为 std::reference_wrapper 已经提供了:

int main (int, char**) {
  int object = 21; // half of the answer
  vector<reference_wrapper<int>> v;
  v.push_back(object);
  v[0].get() = 42; // assignment needs explicit conversion of lhs to a real reference
  cout << "the answer is " << object << endl;
  return 0;
}

(Example live here)

现在有人可能会争辩为什么要像 std::reference_wrapper 这样在指针周围使用包装器,而我们也可以直接使用指针。 IMO 一个指针,能够成为 nullptr,改变了代码的语义:当你有一个原始指针时,它 可能 是无效的。当然,你可以假设它不是,或者把它放在注释的某个地方,但最终你会依赖代码不能保证的东西(这种行为通常会导致错误)。

如果 你的向量的一个元素可以 "reference" 一个对象或者是无效的,那么原始指针仍然不是首选(对我来说):当你使用 vector 中的有效元素,然后它引用的对象实际上是从代码的多个位置引用的;它是共享的。对象的 "main" 引用应该是 std::shared_ptr and the elements of your vector std::weak_ptrs。然后,您可以(线程安全)在需要时获取有效的 "reference"(共享指针)并在完成后将其删除:

auto object = make_shared<int>(42);
vector<weak_ptr<int>> v;
v.push_back (object);

// ... somewhere later, potentially on a different thread
if (auto ref = v[0].lock()) {
  // noone "steals" the object now while it's used here
}
// let them do what they want with the object, we're done with it ...

最后,请对我的回答持保留态度,其中大部分是基于我的意见(和经验),可能不算 "standard practice"。