为什么使用指针向量被认为是不好的
Why is using vector of pointers considered bad?
最近我遇到了一些观点,我不应该使用指针向量。我想知道 - 为什么我不能?
例如,如果我有一个类foo
则可以这样做:
vector <foo*> v;
v.push_back(new foo());
我已经看到有些人投票反对这种做法,这是为什么?
在容器中存储普通指针可能会导致内存泄漏和指针悬空。将指针存储在容器中不会定义指针的任何类型的所有权。因此,容器不知道去存储和复制操作的语义。当元素从容器中删除时,容器不知道如何正确销毁它们,当执行复制操作时,不知道所有权语义。当然,你总是可以自己处理这些事情,但仍然有可能出现人为错误。
使用智能指针将所有权和销毁语义留给他们。
另一件值得一提的事情是,容器分为非侵入式和侵入式连接器 - 它们存储实际提供的对象而不是副本,因此它实际上归结为指针集合。非侵入式指针具有一些优点,因此您不能概括容器中的指针是始终应避免使用的东西,但在大多数情况下仍建议使用。
使用原始指针向量不一定是糟糕的风格,只要您记住指针没有所有权语义。当您开始使用new
和delete
时,这通常意味着您做错了什么。
特别是,在现代C++代码中应使用 new
或delete
的唯一情况是在构造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。我个人不喜欢这种方法,因为共享指针通常很难推理对象的生存期。在此特定示例中,由于Vertexes
之间的循环引用,共享指针根本不起作用。
因为向量的析构函数不会在指针上调用delete
,所以很容易意外泄漏内存。 矢量的析构函数调用矢量中所有元素的析构函数,但原始指针没有析构函数。
但是,您可以使用智能指针向量来确保销毁向量将释放其中的对象。 vector<unique_ptr<foo>>
可以在 C++11 中使用,在 C++98 中使用 TR1 您可以使用vector<tr1::shared_ptr<foo>>
(尽管与原始指针或unique_ptr
相比shared_ptr
开销略有。
Boost 还有一个指针容器库,其中特殊的销毁时删除行为内置于容器本身中,因此您不需要智能指针。
其中一个问题是异常安全。
例如,假设某处抛出异常:在本例中,调用 std::vector
的析构函数。但是此析构函数调用不会删除存储在向量中的原始拥有指针。因此,这些指针管理的资源被泄漏(这些资源可以是内存资源,因此存在内存泄漏,但它们也可能是非内存资源,例如套接字、OpenGL 纹理等(。
相反,如果你有一个智能指针的向量(例如 std::vector<std::unique_ptr<Foo>>
(,则如果调用了向量析构函数,则正确删除向量中的每个指向项(由智能指针安全拥有(,并调用其析构函数。因此,与每个项目关联的资源(在向量中"智能"指向(被正确释放。
请注意,观察原始指针的向量是正常的(假设观察项的生存期超过向量的生存期(。问题在于原始拥有指针。
我将专门讨论负责管理指向对象的生存期的指针向量,因为这是指针向量显然是一个可疑选择的唯一情况。
有更好的选择。具体说来:
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"之类的界面,则会自行记录用户的生命周期管理。当你使用原始指针时,你必须清楚地记录你如何处理对象的生存期管理,并希望正确的人在正确的时间阅读文档。
使用原始指针向量的好处是,在如何处理生存期管理方面具有更大的灵活性,并且可能会摆脱一些性能和空间开销。
使用指针向量绝对没有问题。这里的大多数都建议使用智能指针,但我不得不说,使用没有智能指针的指针向量没有问题。我一直这样做。
我同意 juanchopanza 的观点,问题是你的例子是指针来自新的 foo((。在正常的完全有效的用例中,您可能将对象放在其他集合 C 中,以便在销毁 C 时自动销毁这些对象。然后,在对 C 中的对象执行深入操作的过程中,您可以创建任意数量的其他集合,其中包含指向 C 中的对象的指针。 (如果其他集合使用对象副本,那将浪费时间和内存,而引用集合是明确禁止的。在此用例中,我们永远不希望在销毁指针集合时销毁任何对象。
- 为什么rand()的使用被认为是不好的
- 在这里,当我们比较 if(vc[i]==vc1[i]) 时,它是向量数组. 实际上比较的值是多少,
- 为什么 std::shared_ptr 被认为是"heavy"和"expensive",但 std::array "same perfprmance as plain (c-style) arrays
- 一个C头文件可以被认为是一个接口吗
- 为什么const char*和const char[]作为函数参数被认为是等价的
- 为什么12.0==11.999999999999999999被认为是真的
- 这会被认为是糟糕的编程实践吗?
- 为什么这被认为是恒定的?
- 抽象类/接口中的空方法是否被认为是一种好的做法?
- 为什么 std::accumulate 生成705032704作为输出,而不是向量中元素的总和?
- 如果我具有调用其其他实例之一的超载函数,它是否被认为是递归功能
- std::unique_ptr可以被认为是一个monad吗?
- C++ 指向类的指针和/或引用是否被认为是"movable"?
- 使用自定义宏来简化 cin 和 cout 语句等内容被认为是更好还是更差
- 使用成员函数更改对象或返回并分配它是否被认为是更好的做法?
- 我很难修复我认为是双重免费的东西
- 使用 #define 被认为是"bad practice"吗?
- 是一个被认为是不同类型的班级内部的正向声明
- 这被认为是有效的C 11还是C 14?还是GCC/Clang弄错了
- 为什么使用指针向量被认为是不好的