C ++,从同一个原始对象复制的多个对象中的成员指针的“耦合”

时间:2011-06-01 22:40:25

标签: c++ pointers object dynamic-memory-allocation copying

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

struct s_A {
    bool bin;
    s_A(): bin(0) {}
};

class c_A {
public:
    s_A * p_struct;

    c_A(): p_struct(NULL) {p_struct = new s_A [16];}

    void Reset()
    {
        delete [] p_struct;
        p_struct = new s_A [16];
    }
};

int main () 
{   
    srand(1);
    int x = 30;
    std::vector <c_A> objects;
    objects.assign(x, c_A());
    std::vector <c_A> objects_copy;

    for(int q=0; q < x; q++)
    {
        objects_copy.push_back(objects[ rand() % x ]);
        objects_copy[q].Reset();
    }

    for(int q=0; q < 16; q++)
        for(int w=0; w < x; w++)
        {
            // Assertion should not fail, but it does
            assert(!objects_copy[w].p_struct[q].bin);
            objects_copy[w].p_struct[q].bin = true;
        }
}

不知何故,不同复制对象中的指针最终指向同一个内存,并且断言最终会失败。如果在未复制的vector上运行,则不会发生这种情况。我认为c_A.Reset()应该释放指针(通过delete [])指向一个新数组,但我显然遗漏了一些东西。

1 个答案:

答案 0 :(得分:5)

问题的具体来源是这些行:

objects_copy.push_back(objects[ rand() % x ]);
objects_copy[q].Reset();

问题在于,当您尝试将对象的副本推送到objects_copy时,最终会在objects vector中制作对象的浅表副本。这意味着两个向量中的对象最终将具有彼此复制的指针。因此,当您对Reset objects_copy的元素调用vector时,将释放仍由objects数组的元素指向的内存。

问题是您的c_A班级违反了rule of three。因为您的类封装了资源,所以它需要具有析构函数,复制构造函数和复制赋值运算符。如果您定义这三个函数,那么当您尝试将对象复制到objects_copy vector时,您将能够管理底层资源,可能是通过复制或通过引用计数。有关如何编写这些函数的详细信息,请查看this description如何编写这些函数。

编辑:以下是对正在发生的事情的详细说明:

问题在于,当您向vector添加对象时,实际上并未将该对象存储在vector中。相反,您正在存储该对象的副本。因此,当您编写objects_copy.push_back(objects[ rand() % x ]);时,您不会在vectors中存储相同的对象。相反,您正在创建objects中某个对象的副本,并将其存储在objects_copy中。由于您的c_A类型没有定义复制函数,因此最终会生成对象的浅表副本,从而创建指针的副本。这意味着如果您考虑objects列表中的原始对象及其objects_copy中的相应副本,它们将分别具有相同p_struct指针的副本。当您在Reset objects_copy中的对象上调用vector时,可以释放其指针指向的内存。但是,您没有更新objects中存储的原始对象的指针,因此指针现在指向垃圾内存。尝试使用该指针会导致未定义的行为,从而导致崩溃。

添加复制功能将通过允许您控制复制的方式来解决此问题。如果为c_A定义复制函数,导致副本指向原始对象指向的对象的新副本,则不会发生此问题,因为每个对象都有自己的单独指针。或者,如果您使用引用计数,那么如果您知道某个其他对象指向该资源,则可以通过不删除该资源来避免该问题。

希望这有帮助!