通过成员Erase函数删除自身对象:实现是否合规?是否存在风险?
成员对象自删除的实现安全性问题
我有一个包含成员的容器对象(例如vector),需要通过成员自身的Erase函数将其从容器列表中删除。以下是示例代码:
using namespace std; class CCont; class CMember { public: int m_iIndex; CString m_csName; CCont *m_pParent; CMember(CCont *pParent, CString csName, int iIndex); bool Erase(); //return true, if erasing is successful }; class CCont : protected vector <CMember *> { public: //Add member to list void AddMember(CString csName, int iIndex) { CMember *pMem = new CMember(this, csName, iIndex); push_back(pMem); } //find member in list by index / name a return pointer CMember *FindMember(int iIndex) const { for(auto memb : *this) if(memb->m_iIndex == iIndex) return memb; return NULL; } CMember *FindMember(LPCTSTR lsName) const { for(auto memb : *this) if(memb->m_csName == lsName) return memb; return NULL; } bool EraseMemb(CMember *pMemb) { iterator it; for(it = begin(); it != end(); ++it) if(*it == pMemb) break; if(it == end()) return false; delete *it; erase(it); return true; } }; int main() { CCont cCont; cCont.AddMember(L"Smith", 1); cCont.AddMember(L"Novak", 3); cCont.AddMember(L"Johny", 4); CMember *pSmith = cCont.FindMember(L"Smith"); if(pSmith) pSmith->Erase(); //pSmith is now pointer to non-existent object, but I will not use pSmith nevermore int debug = 0; return 0; } //--------implementation---------- CMember::CMember(CCont *pParent, CString csName, int iIndex) : m_pParent(pParent), m_csName(csName), m_iIndex(iIndex) {} bool CMember::Erase() { return m_pParent->EraseMemb(this); //here I am returning to function of the non-existing object }
在该实现中,程序会在某一时刻回到已不存在的对象的函数上下文。请问这种实现方式是否正确?是否会导致程序出现意外行为?
回答
这种实现不正确,会触发C++标准中的未定义行为,存在严重的安全风险。
问题根源
当调用pSmith->Erase()时,流程是:
CMember::Erase()调用m_pParent->EraseMemb(this)CCont::EraseMemb()中执行delete *it,直接销毁了当前CMember对象,其占用的内存被释放EraseMemb()返回true,回到Erase()函数的return语句- 此时
Erase()函数属于已销毁对象的成员函数,this指针指向的是已释放的内存——虽然这里return只是返回一个布尔值,但标准明确规定:访问已销毁对象的任何部分(包括通过this指针隐式访问)都属于未定义行为。
可能的意外行为
- 内存检测工具(如Valgrind)会报告"使用已释放内存"的错误
- 程序可能直接崩溃,或者在后续执行中出现难以追踪的诡异bug(比如内存被复用后的数据污染)
- 不同编译器的优化策略可能导致不同的异常表现,问题的复现性差
安全的改进方案
可以调整销毁和移除的顺序,确保成员函数返回前对象仍然有效,或者将对象销毁放在函数执行的最后一步:
方案1:先移除容器指针,再销毁对象
修改EraseMemb和Erase的逻辑,让容器先移除指针,再由成员函数自行销毁:
// 修改CCont的EraseMemb函数 bool CCont::EraseMemb(CMember *pMemb) { iterator it = find(begin(), end(), pMemb); if(it == end()) return false; erase(it); // 先从容器移除指针,对象仍存在 return true; } // 修改CMember的Erase函数 bool CMember::Erase() { bool success = m_pParent->EraseMemb(this); if(success) delete this; // 最后一步销毁自己,此时函数即将返回,不会再访问对象成员 return success; }
这种方式下,delete this是C++标准允许的操作,只要执行后不再访问对象的任何成员即可。
方案2:使用智能指针管理内存
改用std::unique_ptr<CMember>存储在容器中,让智能指针自动管理内存,避免手动delete的风险:
class CCont : protected vector<std::unique_ptr<CMember>> { public: void AddMember(CString csName, int iIndex) { emplace_back(std::make_unique<CMember>(this, csName, iIndex)); } bool EraseMemb(CMember *pMemb) { auto it = find_if(begin(), end(), [pMemb](const auto& ptr) { return ptr.get() == pMemb; }); if(it == end()) return false; erase(it); // 移除时智能指针自动销毁对象 return true; } // ... 其他函数调整为适配unique_ptr };
这种方式无需手动管理内存,从根源上避免了野指针和已销毁对象访问的问题。
内容的提问来源于stack exchange,提问作者Michal Hadraba
相关产品推荐
相关产品推荐

