【问题标题】:Standard Practice for Creating a "Vector of References" Using Only the Standard Libraries仅使用标准库创建“参考向量”的标准实践
【发布时间】:2015-11-13 07:39:37
【问题描述】:

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

我有一个C初学者的知识,所以我知道我可以创建一个指向对象的指针,并制作一个指针向量。例如vector<Object *>。但是,似乎pointers 是 C++ 中的 discouraged 和 references are preferred。然而,I cannot make a vector of references。

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

我听说过智能指针。但是,似乎存在多种类型的智能指针。为了这个目的,这不是矫枉过正吗?如果智能指针确实是答案,那么我如何确定使用哪一个?

所以我的问题是:创建指向对象的引用/指针向量的标准做法是什么?

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

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

【问题讨论】:

  • 指针向量没有问题,只要向量不需要担心它们指向的对象的生命周期。
  • 使用智能指针向量代替。我很确定这个问题是重复的。
  • 很抱歉,如果它是重复的。我发现与我类似的唯一问题是建议使用 boost,我希望避免。如果这确实是重复的,请告诉我。
  • 只使用一个指针向量。这里不需要智能指针。一推,您可以使用std::reference_wrapper&lt;Object&gt;。
  • 解决你原来的问题的方法是使用vector::emplace_back,它已经在C++11中引入。它根据参数列表在向量内创建一个对象;例如,v.emplace_back()(带有一个空参数列表)使用默认构造函数(或非类类型的值初始化)在向量中创建一个新对象。该对象不会被复制或移动到向量中,它是就地创建的。

标签: c++ pointers vector reference standard-library


【解决方案1】:

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

我几乎可以肯定这不是您想要或“应该”想要的。请原谅我直接打开我的答案,但除非您有充分的理由这样做,否则您可能不想这样做。

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

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

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 的能力,改变了代码的语义:当你有一个原始指针时,它可能是无效的。当然,您可以假设它不是,或者将其放在 cmets 中的某个位置,但最终您会依赖代码无法保证的东西(这种行为通常会导致错误)。

如果你的向量的一个元素可以“引用”一个对象或者是无效的,那么原始指针仍然不是第一选择(对我来说):当你使用向量中的一个元素时是有效的,那么它所引用的对象实际上是从你的代码的多个地方引用的;它是共享的。然后,对该对象的“主要”引用应该是 std::shared_ptr 和向量的元素 std::weak_ptrs。然后,您可以(线程安全)在需要时获取有效的“引用”(共享指针)并在完成时将其删除:

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 ...

最后,请对我的回答持保留态度,其中大部分是基于我的观点(和经验),可能不算“标准做法”。

【讨论】:

  • std::vector 因为 C++11 不需要 CopyAssignable。我认为你不能拥有引用向量的根本原因是你不能拥有指向引用的指针。因此,placement-new 引用是不可能的,迭代器需要奇怪的解决方法。
猜你喜欢
  • 2017-09-18
  • 1970-01-01
  • 2019-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-07
  • 2015-01-31
  • 1970-01-01
相关资源
最近更新 更多