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

时间:2021-07-04 09:07:55

Recently I've met with opinion that I shouldn't use vector of pointers. I wanted to know - why I cant?

最近我发现我不应该使用向量指针。我想知道——为什么我不能?

For example if I have a class foo it is possible to do this:

例如,如果我有一个类foo,它可以这样做:

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

I've already seen some people down voting such practices, why is that?

我已经看到一些人投票反对这种做法,为什么呢?

6 个解决方案

#1


8  

Storing plain pointers in a container can lead to memory leaks and dangling pointers. Storing a pointer in a container does not define any kind of ownership of the pointer. Thus the container does not know the semantics of desctruction and copy operations. When the elements are being removed from the container the container is not aware how to properly destroy them, when a copy operation is performend no ownership semanctics are known. Of course, you can always handle these things by yourself, but then still a chance of human error is possible.

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

Using smart pointers leaves the ownership and destruction semantics up to them.

使用智能指针将所有权和销毁语义留给它们。

Another thing to mention is that containers are divided into non-intrusive and intrusive contaiers - they store the actual provided object instead of a copy so it actually comes down to a collection of pointers. Non intrusive pointers have some advantages, so you can't generalize that pointers in a container is something that should be avoided in all times, still in most cases it is recommended.

另一件要注意的事情是,容器被划分为非插入式和插入式接触器——它们存储实际提供的对象,而不是副本,因此实际上它可以归结为指针的集合。非侵入性指针有一些优点,所以不能一概而论,容器中的指针在任何时候都应该避免,但在大多数情况下都是推荐的。

#2


8  

Using an vector of raw pointers is not necessary bad style, as long as you remember that the pointers do not have ownership semantics. When you start using new and delete, it usually means that you're doing something wrong.

使用一个原始指针的向量并不一定是糟糕的样式,只要您记住指针没有所有权语义。当你开始使用new和delete时,通常意味着你做错了什么。

In particular, the only cases where you should use new or delete in modern C++ code is when constructing unique_ptr's, or constructing shared_ptr's with custom deleters.

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

For example, assume that we have an class that implemented an bidirectional Graph, a Graph contains some amount of 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);
}

Notice how i have an vector of raw Vertex * in that Vertex class? I can do that because the lifetime of the Vertexes that it points to are managed by the class Graph. The ownership of my Vertex class is explicit from just looking at the code.

注意我如何在那个顶点类中有一个原始顶点*的向量?我可以这样做,因为它所指向的顶点的生命周期由类图来管理。我的顶点类的所有权是显式的,只要看一下代码。

An different answer suggests using shared_ptr's. I personally dislike that approach because shared pointers, in general, make it very hard to reason about the lifetime of objects. In this particular example, shared pointers would not have worked at all because of the circular references between the Vertexes.

另一个不同的答案是使用shared_ptr。我个人不喜欢这种方法,因为总的来说,共享指针使得很难对对象的生命周期进行推理。在这个特殊的例子中,共享指针根本不会起作用,因为在Vertexes之间有循环引用。

#3


7  

Because the vector's destructor won't call delete on the pointers, so it's easy to accidentally leak memory. A vector's destructor calls the destructors of all the elements in the vector, but raw pointers don't have destructors.

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

However, you can use a vector of smart pointers to ensure that destroying the vector will free the objects in it. vector<unique_ptr<foo>> can be used in C++11, and in C++98 with TR1 you can use vector<tr1::shared_ptr<foo>> (though shared_ptr has a slight overhead compared to a raw pointer or unique_ptr).

但是,您可以使用一个智能指针向量来确保销毁该向量将释放其中的对象。向量 >可以在c++ 11中使用,在c++ 98和TR1中可以使用向量< TR1:shared_ptr >(尽管与原始指针或unique_ptr相比,shared_ptr有轻微的开销)。

Boost also has a pointer container library, where the special delete-on-destruction behavior is built into the container itself so you don't need smart pointers.

Boost还拥有一个指针容器库,其中特殊的删除-销毁行为被构建到容器本身中,因此您不需要智能指针。

#4


5  

One of the problems is exception-safety.

其中一个问题是异常安全。

For example, suppose that somewhere an exception is thrown: in this case, the destructor of std::vector is called. But this destructor call does not delete the raw owning pointers stored in the vector. So, the resources managed by those pointers are leaked (these can be both memory resources, so you have a memory leak, but they could also be non-memory resources, e.g. sockets, OpenGL textures, etc.).

例如,假设在某个地方抛出了一个异常:在本例中,调用std::vector的析构函数。但是这个析构函数调用不会删除存储在向量中的原始拥有指针。因此,这些指针管理的资源被泄漏(这些都可能是内存资源,所以您有内存泄漏,但是它们也可能是非内存资源,例如套接字、OpenGL纹理等)。

Instead, if you have a vector of smart pointers (e.g. std::vector<std::unique_ptr<Foo>>), then if the vector's destructor is called, each pointed item (safely owned by a smart pointer) in the vector is properly deleted, calling its destructor. So, the resources associated to each item ("smartly" pointed to in the vector) are properly released.

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

Note that vectors of observing raw pointers are fine (assuming that the lifetime of the observed items exceeeds that of the vector). The problem is with raw owning pointers.

注意观察原始指针的向量是可以的(假设观察到的项的生命周期超过了向量的生命周期)。问题是原始拥有指针。

#5


3  

Because there are much better alternatives. Specifically:

因为有更好的选择。具体地说:

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

and

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

The difference is that the above versions tell the user how the lifetime of the objects is taken care of. Using raw pointers often leads to the pointers being deleted either more or less than once, especially if the code is modified over time.

区别在于,上面的版本告诉用户如何处理对象的生命周期。使用原始指针通常会导致指针被多次或多次删除,特别是当代码随时间而修改时。

When you use raw pointers, you have to be clearly document how you handle the lifetime management of the objects, and hope that the right people read the documentation at the right time. If you use an interface like shared_ptr or unique_ptr this self-documents the lifetime management.

当您使用原始指针时,您必须清楚地记录如何处理对象的生命周期管理,并希望正确的人员在正确的时间阅读文档。如果使用shared_ptr或unique_ptr之类的接口,则该接口将自动记录生命周期管理。

The benefit of raw pointers is that you have more flexibility on how to take care of lifetime management. That additional flexibility is very very rarely necessary.

原始指针的好处是,您可以更灵活地处理生命周期管理。这种额外的灵活性很少是必要的。

#6


0  

There is absolutely no problem in using a vector of pointers. Most here are suggesting smart pointers, but I just have to say, there is no problem with using a vector of pointers without smart pointers. I do it all the time.

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

I agree with juanchopanza that the problem is your example is the pointers come from new foo(). In a normal completely-valid use case, you might have the objects in some other collection C, so that the objects will automatically get destroyed when C is destroyed. Then, in doing in the process of doing in-depth operations on the objects in C, you might create any number of other collections containing pointers to the objects in C. (If the other collections used object copies that would be time and memory wasteful, while collections of references is expressly forbidden.) In this use case, we never want to destroy any objects when a collection of pointers is destroyed.

我同意juanchopanza的观点,问题是你的例子是来自新foo()的指针。在一个正常的完全有效的用例中,您可能拥有其他集合C中的对象,以便当C被销毁时,对象将自动被销毁。之后,在做的过程中做深入的对象上的操作在C语言中,您可以创建任意数量的其他集合包含指针对象的C(如果使用的其他集合对象副本,将会浪费时间和内存,而引用的集合是明令禁止的。)在这个用例中,当指针的集合被销毁时,我们绝不希望销毁任何对象。

#1


8  

Storing plain pointers in a container can lead to memory leaks and dangling pointers. Storing a pointer in a container does not define any kind of ownership of the pointer. Thus the container does not know the semantics of desctruction and copy operations. When the elements are being removed from the container the container is not aware how to properly destroy them, when a copy operation is performend no ownership semanctics are known. Of course, you can always handle these things by yourself, but then still a chance of human error is possible.

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

Using smart pointers leaves the ownership and destruction semantics up to them.

使用智能指针将所有权和销毁语义留给它们。

Another thing to mention is that containers are divided into non-intrusive and intrusive contaiers - they store the actual provided object instead of a copy so it actually comes down to a collection of pointers. Non intrusive pointers have some advantages, so you can't generalize that pointers in a container is something that should be avoided in all times, still in most cases it is recommended.

另一件要注意的事情是,容器被划分为非插入式和插入式接触器——它们存储实际提供的对象,而不是副本,因此实际上它可以归结为指针的集合。非侵入性指针有一些优点,所以不能一概而论,容器中的指针在任何时候都应该避免,但在大多数情况下都是推荐的。

#2


8  

Using an vector of raw pointers is not necessary bad style, as long as you remember that the pointers do not have ownership semantics. When you start using new and delete, it usually means that you're doing something wrong.

使用一个原始指针的向量并不一定是糟糕的样式,只要您记住指针没有所有权语义。当你开始使用new和delete时,通常意味着你做错了什么。

In particular, the only cases where you should use new or delete in modern C++ code is when constructing unique_ptr's, or constructing shared_ptr's with custom deleters.

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

For example, assume that we have an class that implemented an bidirectional Graph, a Graph contains some amount of 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);
}

Notice how i have an vector of raw Vertex * in that Vertex class? I can do that because the lifetime of the Vertexes that it points to are managed by the class Graph. The ownership of my Vertex class is explicit from just looking at the code.

注意我如何在那个顶点类中有一个原始顶点*的向量?我可以这样做,因为它所指向的顶点的生命周期由类图来管理。我的顶点类的所有权是显式的,只要看一下代码。

An different answer suggests using shared_ptr's. I personally dislike that approach because shared pointers, in general, make it very hard to reason about the lifetime of objects. In this particular example, shared pointers would not have worked at all because of the circular references between the Vertexes.

另一个不同的答案是使用shared_ptr。我个人不喜欢这种方法,因为总的来说,共享指针使得很难对对象的生命周期进行推理。在这个特殊的例子中,共享指针根本不会起作用,因为在Vertexes之间有循环引用。

#3


7  

Because the vector's destructor won't call delete on the pointers, so it's easy to accidentally leak memory. A vector's destructor calls the destructors of all the elements in the vector, but raw pointers don't have destructors.

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

However, you can use a vector of smart pointers to ensure that destroying the vector will free the objects in it. vector<unique_ptr<foo>> can be used in C++11, and in C++98 with TR1 you can use vector<tr1::shared_ptr<foo>> (though shared_ptr has a slight overhead compared to a raw pointer or unique_ptr).

但是,您可以使用一个智能指针向量来确保销毁该向量将释放其中的对象。向量 >可以在c++ 11中使用,在c++ 98和TR1中可以使用向量< TR1:shared_ptr >(尽管与原始指针或unique_ptr相比,shared_ptr有轻微的开销)。

Boost also has a pointer container library, where the special delete-on-destruction behavior is built into the container itself so you don't need smart pointers.

Boost还拥有一个指针容器库,其中特殊的删除-销毁行为被构建到容器本身中,因此您不需要智能指针。

#4


5  

One of the problems is exception-safety.

其中一个问题是异常安全。

For example, suppose that somewhere an exception is thrown: in this case, the destructor of std::vector is called. But this destructor call does not delete the raw owning pointers stored in the vector. So, the resources managed by those pointers are leaked (these can be both memory resources, so you have a memory leak, but they could also be non-memory resources, e.g. sockets, OpenGL textures, etc.).

例如,假设在某个地方抛出了一个异常:在本例中,调用std::vector的析构函数。但是这个析构函数调用不会删除存储在向量中的原始拥有指针。因此,这些指针管理的资源被泄漏(这些都可能是内存资源,所以您有内存泄漏,但是它们也可能是非内存资源,例如套接字、OpenGL纹理等)。

Instead, if you have a vector of smart pointers (e.g. std::vector<std::unique_ptr<Foo>>), then if the vector's destructor is called, each pointed item (safely owned by a smart pointer) in the vector is properly deleted, calling its destructor. So, the resources associated to each item ("smartly" pointed to in the vector) are properly released.

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

Note that vectors of observing raw pointers are fine (assuming that the lifetime of the observed items exceeeds that of the vector). The problem is with raw owning pointers.

注意观察原始指针的向量是可以的(假设观察到的项的生命周期超过了向量的生命周期)。问题是原始拥有指针。

#5


3  

Because there are much better alternatives. Specifically:

因为有更好的选择。具体地说:

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

and

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

The difference is that the above versions tell the user how the lifetime of the objects is taken care of. Using raw pointers often leads to the pointers being deleted either more or less than once, especially if the code is modified over time.

区别在于,上面的版本告诉用户如何处理对象的生命周期。使用原始指针通常会导致指针被多次或多次删除,特别是当代码随时间而修改时。

When you use raw pointers, you have to be clearly document how you handle the lifetime management of the objects, and hope that the right people read the documentation at the right time. If you use an interface like shared_ptr or unique_ptr this self-documents the lifetime management.

当您使用原始指针时,您必须清楚地记录如何处理对象的生命周期管理,并希望正确的人员在正确的时间阅读文档。如果使用shared_ptr或unique_ptr之类的接口,则该接口将自动记录生命周期管理。

The benefit of raw pointers is that you have more flexibility on how to take care of lifetime management. That additional flexibility is very very rarely necessary.

原始指针的好处是,您可以更灵活地处理生命周期管理。这种额外的灵活性很少是必要的。

#6


0  

There is absolutely no problem in using a vector of pointers. Most here are suggesting smart pointers, but I just have to say, there is no problem with using a vector of pointers without smart pointers. I do it all the time.

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

I agree with juanchopanza that the problem is your example is the pointers come from new foo(). In a normal completely-valid use case, you might have the objects in some other collection C, so that the objects will automatically get destroyed when C is destroyed. Then, in doing in the process of doing in-depth operations on the objects in C, you might create any number of other collections containing pointers to the objects in C. (If the other collections used object copies that would be time and memory wasteful, while collections of references is expressly forbidden.) In this use case, we never want to destroy any objects when a collection of pointers is destroyed.

我同意juanchopanza的观点,问题是你的例子是来自新foo()的指针。在一个正常的完全有效的用例中,您可能拥有其他集合C中的对象,以便当C被销毁时,对象将自动被销毁。之后,在做的过程中做深入的对象上的操作在C语言中,您可以创建任意数量的其他集合包含指针对象的C(如果使用的其他集合对象副本,将会浪费时间和内存,而引用的集合是明令禁止的。)在这个用例中,当指针的集合被销毁时,我们绝不希望销毁任何对象。