C++自定义istream操纵符实现读5跳5字符功能异常排查
问题说明
你需要实现一个自定义istream流操纵符skipchar,核心逻辑为:从输入流中循环读取5个字符、跳过接下来5个字符,直到输入末尾,预期调用方式如下:
string line; cin >> skipchar >> line;
现有实现无法正常运行,同时要求最终实现不需要引入<sstream>头文件,原有错误代码如下:
struct membuf : streambuf { membuf(char* begin, char* end) { this->setg(begin, begin, end); } }; istream& skipchar(istream& stream) { string temp; getline(stream, temp); char *str = new char[temp.size() + 1]; strcpy(str, temp.c_str());//get string and convert to char* char* res = new char[strlen(str)]; for (int i=0,j=0;i<strlen(str);i++) if ((i / 5) % 2 == 0) //get first 5, then 5 skip, etc res[j++] = str[i]; membuf b(res, res + strlen(res)); //copy str to buffer stream.rdbuf(&b); return stream; } int main() { string str; cout << "enter smth:\n"; cin >> skipchar >> str; cout << "entered string: " << str; return 0; }
原有代码问题分析
- 局部变量悬空:
skipchar函数内定义的membuf对象b是栈上局部变量,函数返回后立刻被销毁,后续流操作访问已释放内存会触发未定义行为。 - 内存泄漏:
new申请的str、res两块堆内存从未被释放,且membuf销毁后没有任何机制回收这部分内存。 - 字符串构造错误:
res数组没有手动添加字符串结束符\0,strlen(res)计算的长度完全不可靠,读取时会出现越界。 - 流状态破坏:直接替换流的
rdbuf会丢失原有流的状态、异常掩码配置,极易触发流错误标记,且提前用getline读完所有输入的逻辑和后续>>运算符的读取逻辑完全不兼容。
正确实现方案
不需要提前把所有输入读入字符串再构造自定义缓冲区,直接实现一层过滤流缓冲区即可,全程不需要引入<sstream>头文件:
#include <iostream> #include <string> #include <streambuf> using namespace std; class skipfilterbuf : public streambuf { private: streambuf* src; int cnt = 0; enum Mode { READ, SKIP } mode = READ; char cur_ch; protected: int underflow() override { while (true) { int c = src->sbumpc(); if (c == EOF) return EOF; if (mode == READ) { cur_ch = static_cast<char>(c); setg(&cur_ch, &cur_ch, &cur_ch + 1); cnt++; if (cnt == 5) { cnt = 0; mode = SKIP; } return c; } else { cnt++; if (cnt == 5) { cnt = 0; mode = READ; } } } } public: skipfilterbuf(streambuf* s) : src(s) {} }; istream& skipchar(istream& stream) { static int buf_idx = ios_base::xalloc(); if (stream.pword(buf_idx) == nullptr) { auto* filter_buf = new skipfilterbuf(stream.rdbuf()); stream.pword(buf_idx) = filter_buf; stream.rdbuf(filter_buf); // 注册流销毁回调,自动释放过滤缓冲区避免内存泄漏 stream.register_callback([](ios_base::event ev, ios_base& s, int idx) { if (ev == ios_base::erase_event) { delete static_cast<skipfilterbuf*>(s.pword(idx)); s.pword(idx) = nullptr; } }, buf_idx); } return stream; } int main() { string str; cout << "enter smth:\n"; cin >> skipchar >> str; cout << "entered string: " << str << endl; return 0; }
实现说明
- 核心过滤逻辑封装在
skipfilterbuf中,继承标准streambuf,逐字符从原输入流读取数据,自动按照读5个、跳5个的规则过滤,不需要提前缓存全量输入。 - 用
ios_base::xalloc()申请流的私有存储位存放过滤缓冲区指针,避免局部变量生命周期结束后出现悬空指针问题。 - 注册流事件回调,在流销毁时自动释放申请的过滤缓冲区内存,无内存泄漏问题。
- 过滤逻辑在流缓冲区层生效,后续不管用
>>读取字符串还是用getline读取行,都会自动应用跳字符规则,兼容性更强。 - 测试验证:输入
0123456789abcdefghij时,最终读取到的结果为01234abcde,完全符合读5跳5的循环逻辑。
内容的提问来源于stack exchange,提问作者nameses
相关产品推荐
相关产品推荐

