C++类内/外创建对象的指针差异及测试失败原因排查
C++多态测试异常分析与修复
问题场景
学习C++多态机制时,编写测试程序遇到异常:
- 在派生类
Square内部创建Item对象并加入集合后,获取的Item指针的Location成员与Square指针不匹配,测试失败; - 在类外部创建
Item并加入集合时完全正常。
相关代码
Shape.h
class Item; class Shape { public: virtual void PrintClassName() = 0; virtual bool AddItem(Item* item) { ItemCollection.push_back(item); item->Location = this; return true; } virtual Item* GetItem() { return *ItemCollection.begin(); } virtual int GetNumItems() { return ItemCollection.size(); } private: std::list<Item*> ItemCollection; };
Item.h
class Shape; class Item { public: Shape* Location; };
Square.h
class Square : public Shape { public: virtual void PrintClassName() { std::cout << "Square" << std::endl; } virtual bool CreateNewItem() { Item newItem; Item* newItem_ptr = &newItem; this->AddItem(newItem_ptr); return true; } };
Main.cpp
// helper function for tests void Assert(std::string description, bool a) { if (a) { std::cout << " Success: [" << description << "]" << std::endl; return; } std::cout << " FAIL: [" << description << "]" << std::endl; return; } int main() { { Square square; Square* square_ptr = □ // Initial state Assert("Initial empty Square", square_ptr->GetNumItems() == 0); // Create Item Item item; Item* item_ptr = &item; Assert("Add item to Square", square_ptr->AddItem(item_ptr)); Assert("Square contains 1 item", square_ptr->GetNumItems() == 1); Assert("Item in Square is the above created item", square_ptr->GetItem() == item_ptr); Assert("Location of the item is the Square", item_ptr->Location == square_ptr); } std::cout << "- - - - - -" << std::endl; { Square square; Square* square_ptr = □ // Initial state Assert("Initial empty Square", square_ptr->GetNumItems() == 0); // Create Item using Square's fuction Assert("Add item to Square", square_ptr->CreateNewItem()); Assert("Square contains 1 item", square_ptr->GetNumItems() == 1); Item* item_ptr = square_ptr->GetItem(); Assert("Location of the item is the Square", item_ptr->Location == square_ptr); } }
测试结果
Success: [Initial empty Square] Success: [Add item to Square] Success: [Square contains 1 item] Success: [Item in Square is the above created item] Success: [Location of the item is the Square] - - - - - - Success: [Initial empty Square] Success: [Add item to Square] Success: [Square contains 1 item] FAIL: [Location of the item is the Square]
两种创建方式的差异
- 外部创建:
Item对象定义在main函数的栈帧中,生命周期覆盖整个测试代码块,断言执行时对象依然有效。 - 内部创建:
Item对象是CreateNewItem函数的局部栈变量,函数执行完毕后,该对象的内存会被栈回收,后续操作可能覆盖这块内存。
测试失败的原因
在Square::CreateNewItem中,newItem是局部栈变量,函数返回后,这个对象的生命周期结束,其对应的地址变成悬空指针。后续从集合中取出这个指针时,指向的内存已经不再是原来的Item对象,里面的Location成员值已经被栈上的其他数据覆盖,自然和Square指针不匹配,导致断言失败。
而外部创建的Item在整个测试块中都处于有效生命周期,指针始终指向合法内存,Location值不会被篡改,所以断言成功。
C++03标准下的修复方案
C++03没有智能指针,需手动管理内存或调整存储方式,核心是让Item的生命周期与集合保持一致:
方案1:动态分配Item对象
修改Square::CreateNewItem,用new在堆上创建Item,避免函数返回时对象被销毁:
virtual bool CreateNewItem() { Item* newItem_ptr = new Item; this->AddItem(newItem_ptr); return true; }
同时需要在Shape的析构函数中手动释放所有Item,防止内存泄漏:
virtual ~Shape() { for (std::list<Item*>::iterator it = ItemCollection.begin(); it != ItemCollection.end(); ++it) { delete *it; } ItemCollection.clear(); }
方案2:直接存储Item对象而非指针
修改Shape类的集合类型为std::list<Item>,让容器管理Item的生命周期:
class Shape { public: virtual void PrintClassName() = 0; // 外部添加Item的方法 virtual bool AddItem(const Item& item) { ItemCollection.push_back(item); ItemCollection.back().Location = this; return true; } // 内部创建Item的方法 virtual bool CreateNewItem() { ItemCollection.push_back(Item()); ItemCollection.back().Location = this; return true; } virtual Item* GetItem() { return &ItemCollection.front(); } virtual int GetNumItems() { return ItemCollection.size(); } virtual ~Shape() {} private: std::list<Item> ItemCollection; };
对应的Square::CreateNewItem可以直接调用基类方法:
virtual bool CreateNewItem() { return Shape::CreateNewItem(); }
这种方式无需手动释放内存,容器会在Shape析构时自动销毁所有Item对象。
内容的提问来源于stack exchange,提问作者spedyco
相关产品推荐
相关产品推荐

