繁体   English   中英

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

[英]C++ vector of objects vs. vector of pointers to objects

我正在使用 openFrameworks 编写一个应用程序,但我的问题不仅仅针对 oF; 相反,这是一个关于 C++ 向量的一般性问题。

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

// 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();

在某处,有人建议我制作一个指向 class 的对象的指针向量,而不是这些对象本身的向量。 我实现了这一点,确实它很有魅力。

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();

我正在为对象动态分配 memory,即ofVideoPlayer = new ofVideoPlayer;

我的问题很简单:为什么使用指针向量有效,什么时候创建对象向量而不是指向这些对象的指针向量?

关于 c++ 中的向量,您必须了解的是,它们必须使用对象的 class 的复制运算符才能将它们输入向量中。 如果您在这些对象中有 memory 分配在调用析构函数时自动释放,这可以解释您的问题:您的 object 被复制到向量中然后被销毁。

如果在 object class 中有一个指向已分配缓冲区的指针,则此 object 的副本将指向同一缓冲区(如果您使用默认复制运算符)。 如果析构函数释放缓冲区,当调用复制析构函数时,原始缓冲区将被释放,因此您的数据将不再可用。

如果您使用指针,则不会发生此问题,因为您通过 new/destroy 控制元素的生命周期,并且矢量函数仅将指针复制到您的元素。

我的问题很简单:为什么使用指针向量有效,什么时候创建对象向量而不是指向这些对象的指针向量?

std::vector就像一个原始数组,当您尝试推入比其当前大小更多的元素时,分配了新的并重新分配。

所以,如果它包含A指针,就好像你在操作一个A*数组。 当它需要调整大小时(你push_back()一个元素已经填充到它的当前容量),它会创建另一个A*数组并从前一个向量复制到A* * 数组中。

如果它包含A对象,那么就像您在操作A数组一样,因此如果发生自动重新分配, A应该是默认可构造的。 在这种情况下,整个A对象也会被复制到另一个数组中。

看到不同? 如果您进行一些需要调整内部数组大小的操作,则std::vector<A>中的A对象可以更改地址。 这就是在std::vector中包含对象的大多数问题的来源。

在没有此类问题的情况下使用std::vector的一种方法是从一开始就分配一个足够大的数组。 这里的关键词是“容量”。 std::vector容量是 memory 缓冲区的实际大小,它将在其中放置对象。 因此,要设置容量,您有两种选择:

1)在构造时调整您的std::vector大小,以从一开始就构建所有 object,并使用最大数量的对象 - 这将调用每个对象的构造函数。

2)一旦构造了std::vector (但其中没有任何内容),请使用它的reserve() function :然后向量将分配足够大的缓冲区(您提供向量的最大大小)。 向量将设置容量。 如果您在此向量中push_back()对象或resize()在您在reserve()调用中提供的大小限制下,它将永远不会重新分配内部缓冲区,并且您的对象不会更改 memory 中的位置,从而指向这些对象总是有效的(一些检查容量变化从未发生的断言是一种很好的做法)。

如果您使用new为对象分配 memory ,那么您就是在堆上分配它。 在这种情况下,您应该使用指针。 但是,在 C++ 中,约定通常是在堆栈上创建所有对象并传递这些对象的副本,而不是传递指向堆上对象的指针。

为什么这样更好? 因为C++没有垃圾回收,所以堆上的对象的memory不会被回收,除非你特意delete object。 但是,堆栈上的对象总是在离开 scope 时被销毁。 如果您在堆栈而不是堆上创建对象,则可以最大限度地降低 memory 泄漏的风险。

如果您确实使用堆栈而不是堆,则需要编写良好的复制构造函数和析构函数。 编写不当的复制构造函数或析构函数可能导致 memory 泄漏或双重释放。

如果您的对象太大而无法有效复制,那么使用指针是可以接受的。 但是,您应该使用引用计数智能指针(C++0x auto_ptr 或 Boost 库指针之一)来避免 memory 泄漏。

vector加法和内部管理使用原始 object 的副本 - 如果获取副本非常昂贵或不可能,则最好使用指针。

如果将vector成员设为指针,请使用智能指针来简化代码并将泄漏风险降至最低。

也许您的 class 没有正确(即深)复制构造/分配? 如果是这样,指针将起作用,但 object 实例不能作为向量成员。

通常我不会将类直接存储在std::vector中。 原因很简单:您不会知道 class 是否派生。

例如:

在标题中:

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 :)
}

使用向量的主要思想是将对象存储在连续空间中,当使用不会发生的指针或智能指针时

这里还需要记住 memory CPU 使用率的性能。

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

所以我可以猜测 std::vector 在向量大小被保留和已知的情况下会更快。 但是,如果我们不知道计划的大小或者我们计划更改对象的顺序,std::vectorstd::unique_ptr<Object> 会更快。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM