【问题标题】:C++ vector of objects vs. vector of pointers to objects对象的 C++ 向量与指向对象的指针向量
【发布时间】:2011-10-01 06:36:25
【问题描述】:

我正在使用 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;

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

【问题讨论】:

  • 我们可以写一些代码吗?仅凭解释有点难以回答。
  • 如果您不发布任何代码,我们无法弄清楚为什么您的代码不起作用!请添加一个展示您的问题的示例。
  • 重要的代码可能不是vector的用法,而是ofVideoPlayer类本身。

标签: c++ pointers vector


【解决方案1】:

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

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

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

【讨论】:

  • a) 但是,有潜在危险的构造:ofVideoPlayer * video = new ofVideoPlayer; 和 push_back 这个局部变量指向某个向量。只有当video 不离开上下文直到vector&lt;ofVideoPlayer*&gt; videos 使用它时才保证工作(例如,它们分配在与示例相同的上下文中)。 b) 按值传递(失败的代码)更安全 - 不明白为什么“您的对象被复制到 vector然后被销毁。”被销毁 - 为什么?应该说,这是错误/没有复制构造函数的问题。
【解决方案2】:

我的问题很简单:为什么要使用 指针向量工作,以及何时 你会创建一个对象向量吗 与指向那些指针的向量相比 对象?

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

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

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

看到区别了吗? 如果您进行一些需要调整内部数组大小的操作,std::vector&lt;A&gt; 中的 A 对象可以更改地址。这就是在 std::vector 中包含对象的大多数问题的根源。

使用std::vector 而不会出现此类问题的一种方法是从一开始就分配一个足够大的数组。 这里的关键字是“容量”。std::vector 容量是用于放置对象的内存缓冲区的实际大小。因此,要设置容量,您有两种选择:

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

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

【讨论】:

  • A 不必是 default 可构造的,顺便说一句
  • 这是一个很好的答案,尤其是关于当向量因对象推回而调整大小时,对向量中对象的内存引用如何指向无效区域的评论
【解决方案3】:

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

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

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

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

【讨论】:

  • std::auto_ptr 是标准的。它将在 C++0x 中弃用,取而代之的是 std::unique_ptr。还会介绍std::shared_ptr、std::weak_ptr等。堆栈分配并不总是可取的,但使用智能指针进行异常安全的堆管理是可取的。最好传递const&amp; 对象以避免复制调用以提高性能。
【解决方案4】:

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

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

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

【讨论】:

  • 我认为,这是一个正确的答案,但不是那些赞成的答案。
【解决方案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 已决定派生。它与不打算派生的通用类(通常没有虚拟)有很大不同。
【解决方案6】:

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

【讨论】:

    【解决方案7】:

    这里还需要牢记CPU的内存使用性能。

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

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

    【讨论】:

      猜你喜欢
      • 2011-02-11
      • 1970-01-01
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-20
      相关资源
      最近更新 更多