c ++使用std :: list隐式复制*this

San*_*ses 2 c++ oop reference list std

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

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
};
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.现在,我可以创建一个独立的花园,或者将其放入列表中.预期的行为是,如果我改变'size'变量,它会随处变化,也会变化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;
}
Run Code Online (Sandbox Code Playgroud)

最终输出如下:

Expected: 1 1, result: 1 1
Expected: 2 2, result: 2 2
Expected: 1 1, result: 1 1
Expected: 2 2, result: 2 1
Run Code Online (Sandbox Code Playgroud)

Lig*_*ica 5

标准容器拥有它们包含的元素.这意味着在插入每个元素时都会复制这些元素.

当你Garden被复制,默认的拷贝构造函数使用,反过来,该Plant构件也默认复制的.但是,这意味着new Plant包含对 的引用Garden.

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

你应该编写一个复制构造函数,Garden以便Plant以各种方式复制它,除了 new Plant应该引用new Garden,而不是只复制旧的引用.

使用新的C++ 11功能实际上可以避免副本和源自它的整个问题:

gardenList.emplace_back(1);
Run Code Online (Sandbox Code Playgroud)

现在,Garden列表中的就地创建,并且将不会进行拷贝.

然而,你应该还是修复底层设计问题Garden,即使你解决问题这种方式.

  • [un?]财富评论的+1 ......调试中最可怕的事情就是什么时候它不起作用.:) (2认同)