当结构体对象超出作用域时,如何删除结构体对象内的指针?

3
我有一个包含指向对象的指针的结构体对象。
struct Track
{
    KalmanFilter* kalmanFilter; //Kalman filter for this track
    ....
};

我通过以下方式实例化结构体和指针对象:
Track track;
track.kalmanFilter = new KalmanFilter(contours[contour].centroid);
....

我会逐帧检查轨迹是否仍然有效。

//Temporary vector to hold alive tracks
std::vector<Track> tempTracks;

for(...)
{
    //If track is valid save into temp tracks
    if(tracks[track].deadTime <= deadTime) tempTracks.push_back(tracks[track]);
}

//Save all temp tracks back into tracks - this removes dead tracks that were in tracks
tracks = tempTracks;

一段时间后,我使用的微控制器内存不足。这是我的代码中唯一的指针,因此我非常确定KalmanFilter指针没有被删除。

到目前为止,我尝试在Track结构体中使用析构函数,但导致微控制器立即崩溃。

//Destructor frees up Kalman filter memory when the Track object goes out of scope
~Track()
{
    if(kalmanFilter) delete kalmanFilter;
} 

我尝试使用unique_ptr,但是我无法使代码编译通过。我找到的所有示例都在实例化unique_ptr指向的对象同时实例化该对象。在我的情况下,我认为我不能这样做。

如何正确处理这种情况?

谢谢

编辑

根据评论,似乎有两个主要观点。

在构造函数中分配内存

我已经重写了我的结构体

struct Track
{
    Track(const Point& initialPosition) : kalmanFilter(initialPosition) {}
    ~Track() {}
    KalmanFilter kalmanFilter; //Kalman filter for this track
};

我通过实例化来使用它

Track track(contours[contour].centroid);

这个正确吗?

寻找更好的删除轨迹方式

在循环过程中,有没有一种最好的方法可以从向量中删除对象而不会打乱索引?使用STL迭代器吗?

我实施了@M.M提供的解决方案,似乎有效。

tracks.erase(std::remove_if(tracks.begin(), tracks.end(), [&](Track const &t){return t.deadTime > deadTime;}));

3
你有没有遵守"三五定律"(Rule of Three)?该法则是关于C++编程中处理资源管理的指导原则。 - NathanOliver
你应该真正遵循@NathanOliver的建议。不要在struct之外管理内存分配,最好将这种行为封装在类中,并且不要公开它。更好的方法是使用标准的C++容器或智能指针,并将内存分配和释放留给该成员处理。 - πάντα ῥεῖ
1
我找到的所有示例都在实例化unique_ptr时同时实例化指向的对象。如果右侧是r-value,可以将unique_ptrs分配给它:myptr = std::make_unique<KalmanFilter>(contours[contour].centroid); 这会自动删除unique pointer的先前内容。 - jaggedSpire
重新制作一个未构造的 unique_ptr 是可能的。你可以简单地使用 std::unique_ptr<int> n; n = std::make_unique<int>(3); 来实现。参考这个例子:https://ideone.com/WjJLdo - Tas
请注意,检查参数在 freedelete 时是否为零/空气始终是不必要的。不要这样做,没有意义。 - Kuba hasn't forgotten Monica
显示剩余7条评论
3个回答

4
您可以使用智能指针:
struct Track
{
    std::unique_ptr<KalmanFilter> kalmanFilter;
....
};

这意味着当 Track 被销毁时,如果智能指针正在管理一个指针,则会调用该指针的 delete 方法。

可以通过以下方式进行分配:

track.kalmanFilter.reset(new KalmanFilter(contours[contour].centroid));

虽然最好是由Track的构造函数完成这项任务。


这个更改将使Track不可复制(但仍可移动)。这是一件好事,因为以前您的代码在被复制时无法正常工作,现在编译器会帮您捕捉到这个问题。

您需要修改您的删除循环来停止对元素进行复制。通常的做法是使用vector::erasestd::remove_if结合使用(后者将选定的元素移动到末尾,而erase则从末尾擦除元素):

tracks.erase( std::remove_if( begin(tracks), end(tracks), 
    [&](Track const &t) { return t.deadTime > deadTime; } ), end(tracks) );

需要包括的内容为<memory><algorithm>


谢谢您的回答,您能详细解释一下最后一部分吗?那段代码会删除死亡时间<= deadtime或死亡时间> deadtime的轨迹吗? - Joseph Roberts
前者。该函数应返回要删除的项目为“true”,否则为“false”。我会编辑。 - M.M
好的,谢谢。我正在将您的解决方案与维基百科条目“v.erase(std :: remove_if(v.begin(),v.end(),is_odd),v.end())”进行比较。为什么您的解决方案在函数后面不需要v.end()? - Joseph Roberts
需要将tracks.end()作为最后一个参数传递给tracks.erase(),否则只会删除一个元素。@JosephRoberts 你确认过了吗? - A.S.H
1
感谢 @A.S.H 很好知道。我还没有进行充分的测试,我需要检查它是否给了我想要的行为,并在需要时尝试使用tracks.erase函数。但是,在tracks构造函数中实例化卡尔曼滤波器确实有效。 - Joseph Roberts

1
你目前消除死亡轨道的方法会导致每个轨道都有很多副本。
其中一个副本的析构函数会在其他副本仍在使用kalman滤波器时尝试释放内存,这可能会导致问题。而且,如果不能保证在分配之前为NULL...可能会删除一个随机指针。
在这种情况下,我会使用共享指针。但说实话,我觉得你存储/使用轨道的整体结构可能有更好的方法,不过我需要考虑一下并了解更多实现细节。

-1

当你使用push_back()时,你会创建原始Track对象的副本。副本和原始对象都指向同一块内存,并且在某个时刻都被销毁,导致同一地址的双重释放。遵循评论中更好的代码组织建议将有助于避免此类错误。


最好让你的回答包含建议,而不是直接引导人们去评论(这些评论可能会被删除,并且本来就不应该包含答案)。如果你愿意,可以通过提及他们的名字来给评论作者以荣誉。 - M.M

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