C++对象的向量与指向对象的指针的向量

49

我正在使用openFrameworks编写一个应用程序,但我的问题不仅限于oF;而是关于C++向量的一个通用问题。

我想创建一个包含另一个类的多个实例的类,并提供一个直观的界面来与这些对象交互。在内部,我的类使用了一个类的向量,但当我尝试使用vector.at()来操作对象时,程序会编译但无法正常工作(在我的情况下,它无法显示视频)。

// instantiate object dynamically, do something, then append to vector
vector<ofVideoPlayer> videos;
ofVideoPlayer *video = new ofVideoPlayer;
video->loadMovie(filename);
videos.push_back(*video);

// access object in vector and do something; compiles but does not work properly
// without going into specific openFrameworks details, the problem was that the video would
// not draw to screen
videos.at(0)->draw();

有人建议我创建一个指向该类对象的指针向量,而不是直接创建该对象的向量。我实施了这个建议,确实像魔法一样奏效了。

vector<ofVideoPlayer*> videos;
ofVideoPlayer * video = new ofVideoPlayer;
video->loadMovie(filename);
videos.push_back(video);
// now dereference pointer to object and call draw
videos.at(0)->draw();

我以前是通过动态分配内存的方式来为对象分配内存,即ofVideoPlayer = new ofVideoPlayer;

我的问题很简单:为什么使用指针向量有效,什么情况下会创建一个对象向量与一个指向这些对象的指针向量?


我们能看一下代码吗?仅凭解释很难回答这个问题。 - MGZero
如果您没有发布任何代码,我们无法弄清楚为什么您的代码无法正常工作!请添加一个展示您问题的示例。 - Cameron
重要的代码可能不是vector的使用,而是ofVideoPlayer类本身。 - Steve Townsend
7个回答

31
关于C++中的向量,需要知道的是它们必须使用对象类的复制运算符才能将其输入到向量中。如果在这些对象中有自动释放内存的内存分配,当调用析构函数时可能会导致问题:您的对象被复制到向量中,然后被销毁。
如果您的对象类中存在指向已分配缓冲区的指针,则该对象的副本将指向相同的缓冲区(如果使用默认复制运算符)。如果析构函数释放缓冲区,则在调用副本析构函数时,原始缓冲区将被释放,因此您的数据将不再可用。
如果使用指针,就不会出现这个问题,因为通过new/destroy控制元素的生命周期,而向量函数仅复制指向您的元素的指针。

1
a) 然而,ofVideoPlayer * video = new ofVideoPlayer; 这种潜在危险的构造方式,如果将这个局部变量 push_back 到某个向量中,只有当 video 不离开上下文直到 vector<ofVideoPlayer*> videos 使用它时(例如,在示例中分配了相同的上下文),才能保证其正常工作。b) 按值传递(失败的代码)更加安全 - 不理解为什么会出现 "你的对象被复制到 vector 然后被销毁" 的情况。被销毁 - 为什么?应该说,这是一个错误或没有复制构造函数的问题。 - John_West

30
我的问题很简单:为什么使用指针的向量可以工作,而何时需要创建一个对象的向量与指向这些对象的指针的向量?
std::vector就像使用new分配的原始数组,并在尝试推入比当前大小更多元素时重新分配。因此,如果它包含A个指针,就好像您正在操作A*的数组。当需要调整大小(在已填满其当前容量的情况下push_back()元素),它将创建另一个A*数组,并从先前向量的A*数组中复制。
如果它包含A个对象,则就像您正在操作A的数组,因此如果发生自动重新分配,A应该可以默认构建。在这种情况下,整个A对象也会被复制到另一个数组中。
看到区别了吗? std :: vector < A >中的A对象可以更改地址,如果执行某些需要调整内部数组大小的操作,则这是大多数包含对象在std :: vector中出现问题的原因。
使用std :: vector的一种方法是从一开始就分配足够大的数组。这里的关键字是“容量”。 std :: vector的容量是它将放置对象的内存缓冲区的实际大小。因此,要设置容量,有两种选择:
1)在构造时对您的std :: vector进行大小调整,以一次性构建所有对象,具有最大数量的对象-这将调用每个对象的构造函数。

2) 一旦构造了 std::vector(但其中没有任何内容),使用其 reserve() 函数:此时向量将分配足够大的缓冲区(您提供了向量的最大大小)。 向量 将设置容量。如果在 reserve() 调用中提供的大小限制下 push_back() 对象或 resize() ,它将永远不会重新分配内部缓冲区,您的对象将不会在内存中改变位置,从而使指向这些对象的指针始终有效(检查更改容量是否发生的某些断言是一个很好的实践)。


顺便说一下,A 不必要求具有默认构造函数。 - RiaD
3
这是一个很好的答案,尤其是关于当向 vector 中添加对象时导致 vector 调整大小并且指向对象的内存引用可能会指向无效区域的评论。 - nurabha

11
如果你使用new为对象分配内存,那么你是在堆上分配内存。在这种情况下,你应该使用指针。然而,在C++中,通常的惯例是创建所有对象于栈上,并传递那些对象的副本而不是传递指向堆上对象的指针。
为什么这样做更好呢?因为C++没有垃圾回收机制,因此除非你明确地delete对象,否则堆上对象的内存不会被回收。但是,当超出作用域时,栈上对象总是会被销毁。如果你在栈上创建对象而不是在堆上创建对象,则可以将内存泄漏的风险降到最低。
如果你选择使用栈而不是堆,你需要编写良好的复制构造函数和析构函数。糟糕的复制构造函数或析构函数可能导致内存泄漏或重复释放。
如果你的对象太大而无法高效地复制,则可以使用指针。然而,你应该使用引用计数智能指针(C++0x auto_ptr或Boost库指针之一),以避免内存泄漏。

1
std::auto_ptr是标准的,但在C++0x中将被弃用,取而代之的是std::unique_ptr。此外,还将引入std::shared_ptrstd::weak_ptr等。尽管堆栈分配并非总是首选,但使用智能指针进行异常安全的堆管理是明智的选择。为了提高性能,最好传递const&对象以避免复制调用。 - AJG85

8
vector的加法和内部管理使用原始对象的副本 - 如果复制很昂贵或不可能,那么使用指针是更可取的。vector成员如果是指针,则使用智能指针可以简化代码并最小化泄漏的风险。
也许你的类没有进行适当的(即深度)复制构造/赋值?如果是这样,指针将起作用,但对象实例作为向量成员则不行。

1
我认为这是一个恰当的答案,但不是那些被点赞的答案。 - John_West

5
通常我不会直接将类存储在std::vector中。原因很简单:你无法知道该类是派生类还是基类。
例如:
在头文件中:
class base
{
public:
  virtual base * clone() { new base(*this); };
  virtual ~base(){};
};
class derived : public base
{
public:
  virtual base * clone() { new derived(*this); };
};
void some_code(void);
void work_on_some_class( base &_arg );

在源代码中:
void some_code(void)
{
  ...
  derived instance;
  work_on_some_class(derived instance);
  ...
}

void work_on_some_class( base &_arg )
{
  vector<base> store;
  ...
  store.push_back(*_arg.clone());
  // Issue!
  // get derived * from clone -> the size of the object would greater than size of base
}

所以我更喜欢使用shared_ptr

void work_on_some_class( base &_arg )
{
  vector<shared_ptr<base> > store;
  ...
  store.push_back(_arg.clone());
  // no issue :)
}

你的“base”被决定为派生类。这与通常不打算派生的一般类大不相同(通常没有虚函数)。 - apple apple

4
使用向量的主要思想是将对象存储在连续的空间中,而使用指针或智能指针则不会发生这种情况。

实际上,从纯语义的角度来看,向量实际上是一个指针,因为这就是向量这个词的意思...它指向某个方向。更好的理解方式是,有三种线性结构可以使用:1)数组 - 具有连续的存储空间,因此对象数组中的所有对象都存储在连续的存储空间中。2)向量 - 具有连续的指针数组,每个指针指向一个单独分配的块。3)链表 - 可以是单链表或双链表。每个元素都指向下一个元素。因此,向量是链表和数组的混合体。 - Ken Kopelson

0

在编程中,还需要考虑CPU的内存使用性能。

  • std::vector向量保证内存块是连续的。
  • std::vectorstd::unique_ptr<Object>将智能指针保持在连续的内存中,但对象的实际内存块可以放置在RAM的不同位置。

因此,我可以猜测当向量的大小被预留并已知时,std::vector会更快。然而,如果我们不知道计划的大小或者我们有改变对象顺序的计划,那么std::vectorstd::unique_ptr<Object>会更快。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接