【问题标题】:What is the correct way to work with an object that was stored in a vector?使用存储在向量中的对象的正确方法是什么?
【发布时间】:2015-11-08 18:39:40
【问题描述】:

来自 Java,我对如何正确访问存储在向量中的对象感到困惑。

在这个测试用例中,我希望两个输出都显示相同的数字 (2):

#include <iostream>
#include <vector>

using namespace std;

class Item {
public:
  int id = 0;
  Item(int id)
  {
    this->id = id;
  }
};

int main()
{
  vector<Item> items;
  Item item = Item(1);
  items.push_back(item);

  Item itemFromVector = items.at(0);
  itemFromVector.id = 2;

  cout << "item: " << item.id << endl;
  cout << "itemFromVector: " <<  itemFromVector.id << endl;
}
// output:
// item: 1
// itemFromVector: 2

【问题讨论】:

  • 你不需要向量来观察这种现象。 Item item = Item(1); Item item2 = item; item2.id = 2; cout &lt;&lt; item.id &lt;&lt; endl;.
  • 在您的测试用例中,item 是一个对象,itemFromVector 是另一个单独的对象,并且向量存储另一个对象,与上述两个不同。每个人都有自己的数据副本。
  • 好吧,但是你如何在 C++ 中做到这一点?我不应该使用矢量吗?
  • 顺便说一句,Item item = Item(1); 是多余的 javaism。 Item item(1); 就足够了。

标签: c++ vector reference


【解决方案1】:

在这种情况下,item.id 等于 1。

这背后的原因是push_back 调用将执行向量中对象的副本(向量中的Itemitem 是两个不同的对象)。

观察你想要达到的目标的一种方法是使用指针:

int main()
{
  vector<Item*> items; //items contains addresses of Item object
  Item item = Item(1);
  items.push_back(&item); // push address of your item

  Item* itemFromVector = items.at(0); // Pointer to item
  itemFromVector->id = 2; // Modify item id attribute

  cout << "item: " << item.id << endl; // Access item id attribute
  cout << "itemFromVector: " <<  itemFromVector->id << endl; // Access item id attribute
}

【讨论】:

  • 好的,目前正在处理这个......看起来指针的行为更像我在 Java 中的引用所习惯的......
  • @Zalumon 刚刚添加了一些 cmets 来提供帮助
  • 好的,我想我明白了。在这种情况下,我将使用指针。非常感谢大家的帮助。不幸的是,我只能接受一个答案。我认为 Jérôme 是第一个使用指针解决方案的人。不过,我对所有答案都投了赞成票。
【解决方案2】:

当你执行时:

items.push_back(item);

item 的副本存储在向量中。 item 和向量中的副本这两个对象是完全独立的对象。对一个的更改不会影响另一个。

如果您希望能够更改其中一个并期望更改反映在另一个中,您需要创建一个指针向量或std::reference_wrappers 的向量。

#include <iostream>
#include <vector>
#include <functional>

class Item {
   public:
      int id = 0;
      Item(int id)
      {
         this->id = id;
      }
};

void test1()
{
   std::cout << "Output from test1() ...\n";
   std::vector<Item*> items;
   Item item(1);
   items.push_back(&item);

   Item* itemFromVector = items.at(0);
   itemFromVector->id = 2;

   std::cout << "item: " << item.id << std::endl;
   std::cout << "itemFromVector: " <<  itemFromVector->id << std::endl;
}

void test2()
{
   std::cout << "Output from test1() ...\n";
   std::vector<std::reference_wrapper<Item>> items;
   Item item(1);
   items.push_back(item);

   Item& itemFromVector = items.at(0);
   itemFromVector.id = 20;

   std::cout << "item: " << item.id << std::endl;
   std::cout << "itemFromVector: " <<  itemFromVector.id << std::endl;
}

int main()
{
   test1();
   test2();
}

输出:

Output from test1() ...
item: 2
itemFromVector: 2
Output from test1() ...
item: 20
itemFromVector: 20

【讨论】:

  • @OliverCharlesworth,感谢您指出错误。我需要彻底检查答案:)
  • @OliverCharlesworth:什么?它接受T const&amp;T&amp;&amp; 的参数,不是吗?
  • @ChristianHackl,该评论是在我彻底修改答案之前。
  • @ChristianHackl:其实你是对的。但是效果是一样的; vector 将复制要插入的参数。
【解决方案3】:
vector<Item> items;

在此代码中,您有一个 Item 类的列表。
当您调用items.push_back(item); 时,您会将item 的副本推送到items 数组中。因此,当您调用 itemFromVector.id = 2; 时,您会更改刚刚放入数组中的副本的 id。

如果要将原始 item 对象复制到数组中并在之后进行编辑,则必须进行以下更改:

vector<Item*> items;
items.push_back(&item);
Item* itemFromVector = items.at(0);
itemFromVector->id = 2;

在第一行中,您告诉向量我们要存储 Item 类的指针,而不是它的副本。
在第二行中,我们通过在变量名前添加&amp;item 的地址推送到我们的数组中。
在第三行中,我们通过在变量名前添加 * 来告诉编译器我们的变量类型是 Item 类的指针。
在第四行中,我们将. 更改为-&gt;,因为我们正在使用指针对象。

【讨论】:

    【解决方案4】:

    由于您来自 Java,因此在 C++ 中使用 references 来获得预期的行为对您来说并不难。

    项目项目 = 项目(1);

    与 Java 不同,这里的 item 不是一个引用,而是一个实际的实例。你可以在没有赋值的情况下立即构造它:

    Item item(1);
    

    items.push_back(item);

    在这里,您需要知道您在向量中推送的内容不是对项目的引用,而是它的副本,一个从项目复制的真实新实例。

    Item itemFromVector = items.at(0);

    在这里,您将 itemFromVector 执行为向量中第一个项目的 reference。但实际上它是从它复制的一个实例。如果要引用,就声明为引用,就这么简单:

    Item& itemFromVector = items.at(0);
    

    总之,在使用 Java 时,一切都是参考。这在 C++ 中是不正确的。只有明确声明为引用时,引用才是引用。

    【讨论】:

    • 这更像是 Java 没有 C++ 风格的引用,Java 所谓的“引用”(NullPointerException 除外)将是 C++ 中的指针。 Java 中的 everything 不是引用(或指针),因为原语不是。
    • @ChristianHackl 一切都是参考只是一句格言,不应从字面上理解。它适用于为操作对象而定义的变量,因为不使用 new 就不能“直接”(实际上是在堆栈上)实例化对象。好吧,再说一遍,这只是一句用来解释与 C++ 的主要区别的格言。 :)
    • 可能会造成混淆的是,在 C++ 术语中,“对象”具有更广泛的含义,并且包含原始类型的实例。
    • @ChristianHackl 在 C++ 中?还是在 Java 中?
    • 在 C++ 中。在像int x = 0; 这样的行中,谈论“int 对象”是正确的。
    【解决方案5】:
      Item(int id)
      {
        this->id = id;
      }
    

    这是典型的 Java 主义。惯用的 C++ 方式是:

    Item(int id) : id(id) {}
    
    Item item = Item(1);
    

    这个也是。惯用的 C++ 方式是:

    Item item(1);
    
    items.push_back(item);
    

    这会创建item副本,并将副本存储在向量中。

    Item itemFromVector = items.at(0);
    

    同样,这会创建第一项的副本并将其存储在itemFromVector 变量中。

    您可能会想到 Java 代码,例如:

    List<Item> list = new ArrayList<Item>();
    list.add(item);
    Item item = list.get(0);
    

    但这是错误的类比。 Java 只是将指针存储在列表中。更合适的 Java 类比是:

    List<Item> list = new ArrayList<Item>();
    list.add(item.clone());
    Item item = list.get(0).clone();
    

    如果您希望 C++ 表现出 Java list.add(item); Item item = list.get(0); 的行为,那么您必须创建一个指针向量:

      vector<Item*> items;
      Item item(1);
      items.push_back(&item);
    
      Item* itemFromVector = items.at(0);
      itemFromVector->id = 2;
    

    但是 C++ 不是 Java,所以这不是一件常见的事情,并且会导致与 C++ 没有任何内置垃圾收集这一事实相关的各种问题。作为一般准则,请尽可能避免使用指针,并特别注意指向本地对象的指针。

    【讨论】:

    • Okidoki,谢谢你在java中的解释,这让我很清楚;-)。但现在我觉得我回到了第一方。如果您不鼓励这种解决方案,那么您有什么建议?
    • @Zalumon:不客气 :) 但是您在寻找什么解决方案?真正的问题是什么?
    • 我的意思是,如果不鼓励指针接近您和概述的其他人,c++ 程序员通常如何处理对象列表?
    • @Zalumon:推荐的方法是只使用T 的向量而不是T* 的向量,直到遇到特定问题并需要更复杂的东西。我的例子是一个特别危险的例子,因为我使用了一个指向本地对象的指针,当本地对象被销毁但指向它的指针仍然挂在其他地方时,这可能会导致问题。请问您认为指针方法如此吸引人的原因是什么?
    • @Zalumon:我认为你应该尝试更多地了解指针、引用和单个对象的动态内存分配。对于容器和一切,可能更难理解。 (另一方面,老实说,我不确定这些天我将如何教授 C++。首先是低级基础还是有用的抽象?)无论如何,如果 Java 程序员在 C++ 中一直犯错,那么这是他们过度使用指针和new
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-05
    • 2012-04-19
    • 1970-01-01
    相关资源
    最近更新 更多