[英]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> 会更快。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.