【问题标题】:How to properly clean up elements from vectors of object pointers如何从对象指针向量中正确清理元素
【发布时间】:2018-10-14 23:52:00
【问题描述】:

我一直在研究动态分配,在 StackOverflow 上遇到了这个问题:

Deallocating objects stored in a vector?

其中一个被赞成的答案解释了如何在使用“指向对象的向量”时手动管理内存:遍历调用 delete 的向量。

我的问题是关于如何删除向量的特定元素,而不是整个向量。在下面的示例程序中,我有一个对象指针向量。想象一下,这些对象的 x 变量随着时间的推移而递减......当一个对象的 x 值达到一个数字(比如说 3)时,我希望删除该对象;但是,我想始终按对象的 x 值保持向量排序。

问题是当我对 x 值达到 3 的对象调用 delete 时,该对象被删除,但那里仍然有一个指向随机内存位置的指针,并且向量的大小也保持不变。

当我遍历打印 x 值的向量时,我调用删除的元素仍然存在,但指向像 -53408995 这样的值。如何摆脱向量的指针元素以及对象?

调用擦除不是一个选项,因为在我的实际程序(不是下面的最小示例)中,向量不断地由改变 x 值等价物的其他因素进行排序。我无法跟踪他们的索引。当我遍历向量以检查 x 值时,我想删除对象和指针元素。

例子:

#include <iostream>
#include <vector>

class A
{
public:
    A(int i) { x = i; }
    int x;
};

int main()
{
    std::vector<A*> Vec;

    Vec.push_back(new A{ 5 });
    Vec.push_back(new A{ 4 });
    Vec.push_back(new A{ 3 });

    std::cout << "Size before = " << Vec.size() << std::endl; // 3

    for (auto& a : Vec)
    {
        std::cout << a->x << std::endl;

        if (a->x == 3) { delete a; }
    }

    std::cout << "Size after = " << Vec.size() << std::endl; // Still 3!

    for (auto& a : Vec)
    {
        std::cout << a->x << std::endl; // Prints 5, 4 and a random memory location like -34528374
    }

    return 0;
}

【问题讨论】:

  • 更改:std::vector&lt;A*&gt; Vec;std::vector&lt;std::unique_ptr&lt;A&gt;&gt; Vec; 工作完成。或者问问自己为什么不只是std::vector&lt;A&gt; Vec;
  • 嗯,差不多完成了;用std::make_unique&lt;A&gt;替换所有new A会更好
  • 该向量还应该包含指向 A 的派生类的指针。对象还需要在各种其他向量/映射中具有指向它们的指针。
  • 在这种情况下,正确的选择是使用 std::list
  • @NathanielG.M. remove_if从向量中输入unique_ptr 会破坏它指向的对象。 remove_if从向量中调用shared_ptr会破坏该对象,如果它是指向该对象的最后一个shared_ptr

标签: c++ pointers vector memory-management dynamic-allocation


【解决方案1】:

在这种情况下,您必须使用 std::list 容器

#include <iostream>
#include <list>

class A
{
public:
    A(int i) { x = i; }
    int x;
};

int main()
{
    std::list<A*> Vec;

    Vec.push_back(new A{ 5 });
    Vec.push_back(new A{ 4 });
    Vec.push_back(new A{ 3 });

    std::cout << "Size before = " << Vec.size() << std::endl;

    for (auto& a : Vec)
    {
        std::cout << a->x << std::endl;
    }

    Vec.remove_if([](A* a){
        bool status = (a->x == 3);
        if(status){
            delete a;
            return true;
        }else{
            return false;
        }
    });

    std::cout << "Size after = " << Vec.size() << std::endl;

    for (auto& a : Vec)
    {
        std::cout << a->x << std::endl;
    }

    return 0;
}

输出:

Size before = 3
5
4
3
Size after = 2
5
4

我重写了你的代码并添加了一些改进

#include <iostream>
#include <list>

class A
{
public:
    A(const int& i):x(i) {} // so X is assigned to i in construction ( more efficient )
    int get_x() const {return x;}
private:
    int x; // x have to be private ( good practice )
};

int main()
{
    std::list<A> List; // A instead of A* so the process of construction / destruction is handled automatically

    List.emplace_back(5); // append element and constructed at the same time
    List.emplace_back(4); // see std::list for more details
    List.emplace_back(3);

    std::cout << "Size before = " << List.size() << std::endl;

    for (auto& a : List)
    {
        std::cout << a.get_x() << std::endl;
    }

    List.remove_if([](A a){ return a.get_x() == 3;});

    std::cout << "Size after = " << List.size() << std::endl;

    for (auto& a : List)
    {
        std::cout << a.get_x() << std::endl;
    }
    return 0;
}

【讨论】:

  • 你为什么命名为std::list Vec?
  • 我只是取了与问题中的代码相同的名称。
  • 我刚把它改成List,谢谢你的评论:)
【解决方案2】:
std::vector<std::unique_ptr<A>> vec;

这将处理正常的删除并通过异常退出。

【讨论】:

  • 这可能行得通,但在我看来,这不是在这种情况下做事的正确方法
【解决方案3】:

您在 cmets 中提到对象将有其他指向它们的指针。这听起来像std::shared_ptr&lt;A&gt; 对我来说。这样,您就可以拥有指向 A 对象的其他指针,而不会出现内存泄漏问题。 (std::shared_ptr 会带来很小的(!)性能成本,但您现在不必担心)。 另外,我更改了您的段落以从矢量中删除/擦除您的元素。请注意,如果有其他实例保留 std::shared_ptr&lt;A&gt; 副本,A 对象仍然存在(但这是一件好事)。

代码如下:

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>

class A
{
public:
    A(int i) { x = i; }
    int x;
};

int main()
{
    std::vector<std::shared_ptr<A>> Vec;
    Vec.emplace_back(std::make_shared<A>(5));
    Vec.emplace_back(std::make_shared<A>(4));
    Vec.emplace_back(std::make_shared<A>(3));

    std::cout << "Size before = " << Vec.size() << std::endl;

    Vec.erase(
        std::remove_if(std::begin(Vec),std::end(Vec), [](auto&& ptr){ return ptr->x == 3;}),
        std::end(Vec));

    std::cout << "Size after = " << Vec.size() << std::endl;
    for (auto&& a : Vec)
    {
        std::cout << a->x << std::endl;
    }

    return 0;
}

【讨论】:

  • 为了调用擦除而在向量中迭代额外的时间是我想避免的,但是如果没有其他方法,那么我会接受这个作为答案并使用你写的 lambda。谢谢。
  • 这不是您需要担心的事情。你不是简单地在向量上迭代 2 次。 remove_if 重新排序您的对象,以便您要删除的所有元素都位于向量的末尾。擦除只是采用这个范围并破坏对象并相应地调整向量的大小。无论如何都必须完成这项工作,因此您不会支付任何性能损失
【解决方案4】:

在研究了一点迭代器之后,我也想出了一个使用原始指针的答案:

for (std::vector<A*>::iterator it = Vec1.begin(); it != Vec1.end(); )
{
    if ((*it)->x == 3)
    {
        delete * it;
        it = Vec1.erase(it);
    }
    else 
    {
        ++it;
    }
}

我将保留 phön 的帖子作为答案,因为如果可用,智能指针应始终优于原始指针。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-31
    • 2016-12-10
    • 1970-01-01
    • 1970-01-01
    • 2013-01-20
    • 1970-01-01
    • 2013-07-05
    • 1970-01-01
    相关资源
    最近更新 更多