C++自定义结构体栈的operator[]重载异常问题排查
自定义栈[]运算符重载异常:stack[i]访问返回随机值
问题描述
我正在学习内存管理,用结构体在堆上实现了一个自定义栈,栈的push/pop功能正常。之后尝试重载[]运算符,希望以O(n)时间访问栈内元素,但发现stack->operator[]调用正常,而stack[i](i≠0)会返回随机数值。
代码示例
#include <iostream> struct stackElem { int v; stackElem *p; stackElem(){ v = 0; p = nullptr; } ~stackElem(){ delete p; p = nullptr; } int& operator[](size_t index) { if(index == 0) return this->v; stackElem *temp = this->p; for (size_t i = 0; i < index; i++){ temp = temp->p; } return temp->v; } //stack[0] returns top, then goes down const int operator[](size_t index) const { if(index == 0) return this->v; stackElem *temp = this->p; for (size_t i = 0; i < index; i++){ temp = temp->p; } return temp->v; } }; void stackPush(stackElem *&top, int val){ stackElem *temp = top; top = new stackElem; top->v = val; top->p = temp; } //First in, last out int stackPop(stackElem *&top){ int ret = top->v; stackElem *temp = top->p; top->p = nullptr; delete top; top = temp; return ret; } int main(){ stackElem *list = new stackElem; stackPush(list, 13); stackPush(list, 14); stackPush(list, 15); stackPush(list, 16); stackPush(list, 17); stackPush(list, 18); std::cout<<list->p->v<<std::endl; std::cout<<list[0].v<<" "<<list[1].v<<std::endl;//<<list[2].v<<list[3].v<<list[4].v<<list[5].v; list->operator[](1) = 100; //This works correctly int test = list->operator[](1); //This works correctly int test2 = list[1]; //This breaks std::cout<<test<<std::endl; std::cout<<list[1]; //This breaks return 0; }
补充说明:我重载了两个版本的[]运算符(分别用于int = stack[]和stack[] = int场景),const版本中无法用stackElem *temp = this,因为“不能用const stackElem *初始化stackElem *”,因此使用了特殊的if(index == 0)逻辑并将temp赋值为this->p。
问题根源
核心问题在于指针的原生下标行为和你重载的成员函数完全无关:
list是stackElem*类型,当你写list[i]时,C++会将其解析为*(list + i)——也就是从list指向的内存地址向后偏移i * sizeof(stackElem)字节,直接访问该内存区域的stackElem对象。这和你遍历链表的重载逻辑毫无关系,访问的是堆上不属于你链表结构的内存,因此返回随机值。- 而
list->operator[](i)是正确调用了你在stackElem中重载的成员函数,会遍历链表找到对应元素,所以行为正常。
解决方案
将栈的逻辑封装到一个独立的Stack类中,让类本身重载operator[],而非让底层节点结构体或指针承担这个功能。这样Stack对象的[]调用会触发你自定义的逻辑,而非指针的原生偏移行为。
修改后的代码示例:
#include <iostream> #include <stdexcept> // 用于std::out_of_range struct stackElem { int v; stackElem *p; stackElem() : v(0), p(nullptr) {} ~stackElem() { delete p; } // delete nullptr是安全的,无需额外赋值 }; class Stack { private: stackElem* top; public: Stack() : top(new stackElem) {} ~Stack() { delete top; } void push(int val) { stackElem* temp = top; top = new stackElem; top->v = val; top->p = temp; } int pop() { if (!top) throw std::out_of_range("Pop from empty stack"); int ret = top->v; stackElem* temp = top->p; top->p = nullptr; // 避免析构时递归删除后续元素 delete top; top = temp; return ret; } // 非const版本,支持赋值操作 int& operator[](size_t index) { stackElem* current = top; for (size_t i = 0; i < index; ++i) { if (!current) throw std::out_of_range("Stack index out of range"); current = current->p; } if (!current) throw std::out_of_range("Stack index out of range"); return current->v; } // const版本,仅支持只读访问 int operator[](size_t index) const { const stackElem* current = top; for (size_t i = 0; i < index; ++i) { if (!current) throw std::out_of_range("Stack index out of range"); current = current->p; } if (!current) throw std::out_of_range("Stack index out of range"); return current->v; } }; int main() { Stack list; list.push(13); list.push(14); list.push(15); list.push(16); list.push(17); list.push(18); std::cout << list[1] << std::endl; // 正常输出17 list[1] = 100; int test = list[1]; std::cout << test << std::endl; // 输出100 std::cout << list[1] << std::endl; // 输出100 return 0; }
额外优化点
- 增加越界检查,抛出
std::out_of_range异常,避免空指针访问导致的未定义行为; - 封装栈的操作到
Stack类中,符合面向对象的封装原则,隐藏底层实现细节; - 使用构造函数初始化列表替代赋值,更符合C++编码规范;
- 简化析构函数逻辑,
delete nullptr是安全操作,无需额外赋值为nullptr。
内容的提问来源于stack exchange,提问作者n0rmi
相关产品推荐
相关产品推荐

