这就是我为了重现测试所做的——写mcve.cc:
#include <iostream>
int main()
{
for (char i = 0;; ++i) std::cout << (int)i << std::flush;
return 0;
}
我在VS2013(调试模式)下编译并启动。它开始“爆出”数字。
我单击控制台窗口并停止输出(如 OP 所述)。按 ESC 后,我期待更多的数字,但什么也没发生。
我暂停了调试并查看了调用堆栈,但没有任何异常。稍微走一步,它甚至看起来代码仍在执行。 (i 的计数仍然发生,正如我在调试器的自动显示中看到的那样。)
于是,我开始应用另一个 Q/A SO: How to disable user selection in Windows console 的解决方案。尽管如此,它似乎是值得的,没有MCVE。所以,我不得不使用 google 和 MSDN 来完成它:
#include <iostream>
#include <Windows.h>
int main()
{
// disable QuickEdit mode in Console
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
DWORD prev_mode;
GetConsoleMode(hInput, &prev_mode);
SetConsoleMode(hInput, prev_mode & ~ENABLE_QUICK_EDIT_MODE);
// start test
for (char i = 0;; ++i) std::cout << (int)i << std::flush;
// done (never reached)
return 0;
}
这有效 - 禁用快速编辑。 (单击控制台窗口不再停止输出。)
但是,如果没有这个技巧,它也应该可以工作。 (我不明白这一点很困扰我。)想了一会儿,我想到了一个启发性的想法。难道std::cout在QuickEdit之后是bad()?
所以,我制作了第三个版本。因为我不能使用coutput,所以我修改了i,我可以在调试器中看到它。 (其实std::cout::good()的返回也显示了,但是分配给i就更能说明问题了。)
#include <iostream>
#include <Windows.h>
int main()
{
for (char i = 0;; ++i) {
if (!std::cout.good()) i = 0;
std::cout << (int)i << std::flush;
}
return 0;
}
在快速编辑选择和 ESC 之后,i 一直是0。因此,另一个解决方法很明显:std::cout 应该定期为clear()ed:
#include <iostream>
#include <Windows.h>
int main()
{
for (char i = 0;; ++i) {
if (!std::cout.good()) std::cout.clear();
std::cout << (int)i << std::flush;
}
return 0;
}
我不确定我更喜欢这两种解决方案中的哪一种:
- 前者侵入性最小(只是在
main() 开头的一个补充)。
- 后者是我通常更喜欢的纯 C++(没有特定于平台的代码)。
关于非 Windows 平台的评论会很有趣...
我不记得我曾经在 Linux(也没有 Irix 或 Solaris——我过去使用过的操作系统)上看到过这样的 QuickEdit 问题。在那个系统上,选择由 Xterm/X11 处理(在我的例子中)——超出了流 I/O 的范围。
那么,std::cout 是否有可能在该系统上变坏(假设输出中没有编码错误)?
最后,我找到了一种可移植的非侵入式方法(以多线程为代价):
#include <atomic>
#include <iostream>
#include <thread>
int main()
{
// spawn extra thread to clean cout periodically
std::atomic<bool> exitThreadClearCOut = false;
std::thread threadClearCOut([&]() {
while (!exitThreadClearCOut) {
if (!std::cout.good()) std::cout.clear();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 100 ms - nearly non-perceptable for humans but an "eternity" for modern CPUs
}
});
// start main work
for (char i = 0;; ++i) {
std::cout << (int)i << std::flush;
}
// finish/join thread to clean cout periodically
exitThreadClearCOut = true;
threadClearCOut.join();
// done
return 0;
}
它启动一个额外的线程来定期检查/清理std::cout。这是必须添加到 main() 的其他内容(我认为是“非侵入性修复”)——实际代码库不需要更改。
注意:我有点怀疑并发访问std::cout 是否安全(尽管我相信它是安全的)。关于这一点,我找到了另一个 Q/A SO: Is cout synchronized/thread-safe?。根据此链接中接受的答案,保证(或至少需要)从 C++11 开始。