为什么使用指针向量被认为是坏的?

时间:2014-12-13 15:20:12

标签: c++ pointers vector std

最近我认识到我不应该使用指针向量。 我想知道 - 为什么我不能?

例如,如果我有一个班级foo,则可以这样做:

vector <foo*> v;
v.push_back(new foo());

我已经看到有些人投票支持这种做法,为什么会这样?

6 个答案:

答案 0 :(得分:11)

使用原始指针的向量不一定是坏的样式,只要你记住指针没有所有权语义。当您开始使用newdelete时,通常意味着您做错了。

特别是,您应该在现代C ++代码中使用newdelete的唯一情况是构建unique_ptr,或者使用自定义删除器构建shared_ptr。

例如,假设我们有一个实现双向Graph的类,Graph包含一定数量的Vertexes

class Vertex 
{
public: 
    Vertex();
    // raw pointer. No ownership
    std::vector<Vertex *> edges;
}

class Graph 
{
public:
    Graph() {};

    void addNode() 
    {
        vertexes.push_back(new Vertex); // in C++14: prefer std::make_unique<>
    }

// not shown: our Graph class implements a method to traverse over it's nodes
private:
    // unique_ptr. Explicit ownership
    std::vector<std::unique_ptr<Vertex>> vertexes;
}

void connect(Vertex *a, Vertex *b) 
{
    a->edges.push_back(b);  
    b->edges.push_back(a);
}

注意我在Vertex类中有一个原始Vertex *的向量?我可以这样做,因为它指向的Vertexes的生命周期由类Graph管理。只需查看代码,我的Vertex类的所有权就是明确的。

另一个答案建议使用shared_ptr&#39。我个人不喜欢这种方法,因为共享指针通常很难推断出对象的生命周期。在这个特定的例子中,由于Vertexes之间的循环引用,共享指针根本不起作用。

答案 1 :(得分:10)

在容器中存储普通指针会导致内存泄漏和悬空指针。将指针存储在容器中并不会定义指针的任何类型的所有权。因此容器不知道解构和复制操作的语义。当从容器中移除元素时,容器不知道如何正确地销毁它们,当执行复制操作时,不知道所有权的语义。当然,你总是可以自己处理这些事情,但是仍然有可能发生人为错误。

使用智能指针将所有权和破坏语义留给他们。

另外要提到的是容器被分为non-intrusive and intrusive contaiers - 它们存储实际提供的对象而不是副本,因此它实际上归结为一组指针。非侵入式指针有一些优点,因此您无法概括容器中的指针是应该始终避免的,但在大多数情况下仍然建议使用。

答案 2 :(得分:8)

因为向量的析构函数不会在指针上调用delete,所以很容易意外泄漏内存。向量的析构函数调用向量中所有元素的析构函数,但原始指针没有析构函数。

但是,您可以使用智能指针向量来确保销毁向量将释放其中的对象。 vector<unique_ptr<foo>>可以在C ++ 11中使用,而在带有TR1的C ++ 98中,您可以使用vector<tr1::shared_ptr<foo>>(尽管shared_ptr与原始指针或{{1}相比具有轻微的开销}})。

Boost也有一个pointer container library,其中特殊的delete-on-destruction行为内置于容器本身,因此您不需要智能指针。

答案 3 :(得分:5)

其中一个问题是异常安全

例如,假设在某处抛出异常:在这种情况下,调用std::vector的析构函数。但是这个析构函数调用删除存储在向量中的原始拥有指针。因此,这些指针管理的资源是泄露(这些可能是内存资源,因此您有内存泄漏,但它们也可能是非内存资源,例如套接字,OpenGL纹理等。 )。

相反,如果你有一个智能指针的向量(例如std::vector<std::unique_ptr<Foo>>),那么如果调用了向量的析构函数,那么每个指向的项目(由智能安全拥有)向量中的指针)被正确删除,调用它的析构函数。因此,与每个项目关联的资源(&#34;智能地&#34;在向量中指向)都被正确释放。

注意观察原始指针的向量很好(假设观察项的生命周期超过向量的生命周期)。问题出在 raw 拥有指针。

答案 4 :(得分:4)

我将特别谈论一个指针向量,它负责管理指向对象的生命周期,因为这是指针向量显然是一个值得怀疑的选择的唯一情况。

还有更好的选择。具体做法是:

std::vector<std::shared_ptr<foo>> v;

std::vector<std::unique_ptr<foo>> v;

boost::ptr_vector<foo> v; // www.boost.org

以上版本告诉用户如何处理对象的生命周期。使用原始指针可能会导致指针被删除多于或少于一次,特别是如果代码随着时间的推移而被修改,或者涉及到异常。

如果您使用'shared_ptr或'unique_ptr'之类的界面,则会自行记录用户的生命周期管理。当您使用原始指针时,您必须清楚地记录您如何处理对象的生命周期管理,并希望合适的人员在正确的时间阅读文档。

使用原始指针向量的好处是,您可以更灵活地处理生命周期管理,并且可以摆脱一些性能和空间开销。

答案 5 :(得分:0)

使用指针向量绝对没有问题。大多数人都建议使用智能指针,但我不得不说,使用没有智能指针的指针向量没有问题。我一直这样做。

我同意juanchopanza的问题是你的例子是指针来自新的foo()。在正常的完全有效的用例中,您可能在其他集合C中拥有对象,以便在销毁C时对象将自动销毁。然后,在对C中的对象进行深入操作的过程中,您可以创建任意数量的其他集合,其中包含指向C中对象的指针。(如果其他集合使用的对象副本会浪费时间和内存虽然明确禁止引用集合。)在这个用例中,当指针集合被销毁时,我们永远不想破坏任何对象。