【问题标题】:c++ Implicit copy of *this with std::listc++ *this 与 std::list 的隐式副本
【发布时间】:2014-08-10 00:28:21
【问题描述】:

对于一个项目,我有一个对象列表(在我的示例代码中,一个花园)。每个花园都包含一个植物,它引用了它所在的花园。这在制作单个花园时非常有效,但是当我创建一个花园对象的 std::list 时,突然在某处创建了一个副本 em> 我不知道,我也不知道如何解决它。对象如下:

struct Garden; //Forward declaration
struct Plant {
    Plant(Garden & myGarden) : theGarden(myGarden) { }; //Constructor
    Garden & theGarden; //reference to garden this Plant is in
};
struct Garden {
    Garden(int size) :  thePlant(*this), size(size) { }; //Constructor
    Plant thePlant; //Plant contained in this Garden
    int size;       //Size of this garden
};

到目前为止一切顺利。现在,我可以单独创建一个 Garden,或者将其放入列表中。预期的行为是,如果我更改“大小”变量,它会随处更改,在theGarden 中也是如此。但是,在列表中,它只会在“原始”Garden 中更改,而不是在参考 theGarden 中更改

int main() {
    //Works
    Garden oneGarden(1);
    std::cout << "Expected: 1 1, result: "
            << oneGarden.size << " "
            << oneGarden.thePlant.theGarden.size << std::endl;
    oneGarden.size = 2;
    std::cout << "Expected: 2 2, result: "
            << oneGarden.size << " "
            << oneGarden.thePlant.theGarden.size << std::endl;

    //Does not work!
    std::list<Garden> gardenList;
    gardenList.push_back(Garden(1));
    std::cout << "Expected: 1 1, result: "
            << gardenList.front().size << " "
            << gardenList.front().thePlant.theGarden.size << std::endl;

    gardenList.front().size = 2;
    std::cout << "Expected: 2 2, result: "
                << gardenList.front().size << " "
                << gardenList.front().thePlant.theGarden.size << std::endl;

    return 0;
}

最终输出如下:

Expected: 1 1, result: 1 1
Expected: 2 2, result: 2 2
Expected: 1 1, result: 1 1
Expected: 2 2, result: 2 1

【问题讨论】:

  • 当花园被复制时,它们的植物将始终参考原始花园。这就是引用的工作方式。不能让它们指向其他东西。
  • 鉴于“突然在我不知道的地方创建了一个副本”,问题真的是副本在哪里吗?

标签: c++ list oop reference std


【解决方案1】:

标准容器拥有它们所包含的元素。这意味着当你插入它们时,每个元素都会被复制。

当您的Garden 被复制时,会使用默认的复制构造函数,而Plant 成员也会被默认复制。但是,这意味着新的Plant 包含对 Garden 的引用。

在这种情况下,旧的GardengardenList.push_back(Garden(1)) 中的临时地址,所以它不仅不是正确的Garden,而且是一个甚至不再存在的Garden。简而言之,您正在通过一个悬空引用(具有未定义的行为)来读取大小,并获得 [un?] 幸运地看到它背后的旧值。

您应该为Garden 编写一个复制构造函数,以各种方式复制其Plant除了新的Plant 应该引用新的Garden,而不是仅仅复制旧参考。

使用新的 C++11 功能实际上可以避免复制和由此产生的整个问题:

gardenList.emplace_back(1);

现在,the Garden in the list is created in-place,不会进行复制。

但是,您应该仍然使用Garden 修复底层设计问题,即使您以这种方式解决问题。

【讨论】:

  • +1 for the [un?]fortune remark... 调试中最可怕的事情是当某些事情在不应该工作的情况下工作。 :)
  • 我还需要覆盖operator=()吗?
  • 我必须补充一点:他用未完全构建的花园初始化植物,这很危险。我想你的编译器会给你一个关于这个引用成员的警告。他应该考虑编写复制/移动构造函数/运算符以及更改对指针的引用。 unique_ptr 在这里可以派上用场。
  • @Zorgiev 我也担心这一点,但大多数消息来源认为如果小心操作,这是安全的;编译器也觉得这很好,甚至没有“注释”。我发现“更改对指针的引用”有点傻,因为它们根本不一样,在这种情况下,选择一个引用是有充分理由的(在这个例子中没有显示)。
  • @sanchises:再一次很难说,但是是的,它会很相似
猜你喜欢
  • 2018-06-06
  • 1970-01-01
  • 2011-04-12
  • 1970-01-01
  • 2012-05-07
  • 1970-01-01
  • 2010-09-19
  • 1970-01-01
  • 2010-10-31
相关资源
最近更新 更多