在std::vector对象(不是指针)上调用push_back会产生严重的副作用.所以指针会更好

Calling push_back on a std::vector of objects (not pointers) has serious side effects. So would pointers be better?

本文关键字:指针 更好 副作用 back push 对象 vector std 调用      更新时间:2023-10-16

这是我上一个问题的延续,在那里我提供所有信息的速度很慢,所以我正在创建一个新的问题,希望得到更多的输入。鉴于:

    struct otherClass {
        ImportantObj    *ptrToImpObj;
        otherClass() {ptrToImpObj = NULL;}
    };
    struct anEntry {
        Thing *thing;
        std::vector<otherClass> *iDM2P2H;
        anEntry(Thing *tg, std::vector<sqdDMPair> *dM2Pair = NULL)
            : thing(tg), iDM2P2H(dM2Pair) {}
        ~anEntry() {delete iDM2P2H;}
    };
    std::vector<anEntry *> aQueue;
    std::vector<anEntry> bQueue;
    void aMethod () {
        Thing thingy = &(masterArrayOfThings[42]);
        aQueue.push_back(new anEntry(thingy, iDM2P2H));
    }
    void bMethod () {
        Thing thingy = &(masterArrayOfThings[42]);
        bQueue.push_back(anEntry(thingy, iDM2P2H));
    }

第二个方法将在复制构造函数涉及的两个对象之间共享的内存中调用dr。

在这种情况下,我觉得我应该在vector中使用指针,aQueue比bQueue更可取。

谢谢。

_ 编辑 _

假设我有20个aQueue,它们将被清除,iDM2P2H 将被替换,每秒数百次(数千次?),因为AI路线评估认为合适。

关于删除iDM2P2H,无论是现在还是将来,您的程序将导致该错误。如果在两个对象中设置相同的指针,那么它们迟早都会死亡,并且它们的析构函数将尝试delete相同的内存。如果您使用指针和new对象,当您删除anEntry对象时,问题仍然存在。

解决方案就是避免在anEntry析构函数中删除iDM2P2H,并在创建它的人的同一上下文中删除它。也就是说,如果它是在程序启动时创建的,您可以在完成对它的需要时在程序的主执行路径中删除它。

您的问题是您的anEntry复制构造函数坏了。默认的复制构造函数(anEntry (const anEntry &))简单地复制所有成员;对于类的显式析构函数,这会导致双重释放。这同样适用于默认的operator=。根据三法则,如果你定义了析构函数、复制构造函数和operator=中的任何一个,你通常应该实现其他两个(或者通过将它们设为私有和未实现来禁止它们);否则,其中一个的默认实现很可能会导致这样的问题。

现在,vector类需要一个工作的复制构造函数。这是vector合同的一部分。如果你的类有一个缺失的复制构造函数(即,通过将其设置为私有而禁止),编译器将出错,防止这些"严重的副作用"。如果类有一个破碎的复制构造函数,那么,这不是vector的错。

注意,您可能需要考虑在anEntry类中使用raii风格的分配。例如,将iDM2P2H改为std::vector<otherClass>,而不是std::vector<otherClass> *。通过这样做,您根本不需要析构函数,如果您可以接受默认的复制语义,那么在这种情况下,您可以使用默认的复制构造函数。

也就是说,vector的复制有时确实会带来很大的开销。你可以做很多事情来解决这个问题;但是,我建议使用而不是原始的std::vector<anEntry *>,原因是这不会自动清除指向元素。

相反,使用std::vector<std::unique_ptr<anEntry>>(如果您有c++ 0x编译器)或boost::ptr_vector<anEntry>。这将为您提供vector的自动销毁好处,但不会复制任何元素(因为向量是指向对象的指针向量)。注意,在unique_ptr的情况下,您需要使用std::move来向向量添加元素。

或者,如果编译器支持c++ 0x移动感知容器,您可以编写一个移动构造函数:

struct anEntry {
    Thing *thing;
    std::vector<sqdDMPair> iDM2P2H;
    anEntry(Thing *thing_, std::vector<sqdDMPair> *vec = NULL)
      : thing(thing_), iDM2P2H(vec ? *vec : std::vector<sqdDMPair>())
    { }
    // Default copy constructor and destructor OK
    // Move constructor
    anEntry(anEntry &&src)
      : thing(src.thing), iDM2P2H(std::move(src.iDM2P2H)) { }
    anEntry &operator=(anEntry &&other) {
      if (this != &other) {
        thing = other.thing;
        iDM2P2H = std::move(other.iDM2P2H);
      }
    }
};

使用std::move允许将iDM2P2H的内容移动到外部向量中的新位置,而不需要递归地复制。但是,对c++ 0x的支持仍处于初级阶段,您的编译器和STL可能还不支持它(如果std::vector<std::unique_ptr<int>>可以编译,您可能还可以)。

这取决于你的对象有多昂贵和有多大。如果对象很小,拥有一个对象向量是可以的,但是如果它们很大或者很昂贵,所有的复制/构造/析构操作加起来会导致很大的性能损失,在这种情况下,你应该使用指针(然后你必须为所有对象管理内存,等等)。

1如果一个类没有很多数据成员(所以它的大小不大),但是管理着昂贵的资源,当它被复制、销毁或创建时,这些资源会发生变化,那么这个类就会小而昂贵。

如果你真的有对象包含指向共享数据/资源的指针,也许你可以考虑使用std::shared_ptr<my_shared_type>,而不仅仅是在dtor中调用delete

使用指针的std::vector<>可能会使问题暂时消失,但它可能只是掩盖了共享资源管理的一个更基本的问题。