You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 = &square;

        // 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 = &square;

        // 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 05:40:22