std::getline(std::cin, string) 从键盘输入可能失败的方式

3
我正在编写这个函数来请求特定的输入类型。is_type 仅验证接收到的字符串是否可以使用 stringstream 转换为所需的类型。
template<typename T>
T get_type(std::string prompt)
{
    T output;
    std::cout << prompt;
    std::string Input;
    while (std::getline(std::cin, Input) && !is_type<T>(Input))
    {
            std::cout << "Invalid input type. Please try again:\n"
              << prompt;
    }

    std::stringstream(Input) >> output;
       return output;
}

这个函数看起来在正常情况下都能按照预期工作,但是当我输入ctrl + Z时出现了问题。应该如何处理这种情况呢?

我添加了:

template<typename T>
    T get_type(std::string prompt)
    {
        T output;
        std::cout << prompt;
        std::string Input;
        while (std::getline(std::cin, Input) && !is_type<T>(Input))
        {
                std::cout << "Invalid input type. Please try again:\n"
              << prompt;
        }
        if (!std::cin)
        {
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        output = get_type<std::string>(prompt) ;
        return output;
        }
        std::stringstream(Input) >> output;
           return output;
    }

当使用例如ctrl+Z的键盘输入后,需要再次请求输入。那么这样解决了std::getline(std::cin, std::string)在用户键盘输入下失败的问题吗?

另外,为什么我必须按两次回车键才能完成

output = get_type<std::string>(prompt) ; 

运行在 if 语句内部的代码行。


你的函数会返回一个值。如果它无法从标准输入读取一行,并且无法返回有意义的默认值,我认为唯一合适的做法是引发异常。 - Remy Lebeau
此外,您的 is_type 看起来是多余的,因为您可以在循环中检查 stringstream::operator>> 的结果,而不是在循环结束后进行检查。 - Remy Lebeau
如果无法获取值(因为输入是eof),我使用第二段代码继续询问。但这样安全吗?在使用getline(cin,string)时,键盘输入还有哪些其他失败的方式? - Daniel Duque
你的初始代码在^Z上会做什么,而你不希望它做什么? - Davis Herring
1个回答

0

std::getline 可能会失败,如果您之前使用了 stdin 而没有清除 failbit,并且输入超过了 std::string::max_size(请参见 Davis Herring 的评论)。 否则,我不知道有什么方法可以让 std::getline 失败,除非是通过 EOF (^Z/^D)。

但是,这里是您的代码,带有一些小的改进:

template<typename T>
T get_type(std::string prompt)
{
    T output;
    std::string input;
    while(true)
    {
        std::cout << prompt;
        std::getline(std::cin, input);
        std::istringstream iss(input);
        if(!std::cin)
        {
            std::cin.clear();
        //  std::clearerr(stdin);
        }
        else if(iss >> output && iss.eof())
            return output;

        std::cout << "Invalid input type. Please try again:\n";
    }
}

如评论中所述,某些系统上需要在stdin上使用clearerr。如果您的系统需要,请取消注释std::clearerr(stdin);

由于您的2x <Enter>问题:忽略语句是不必要的。您只需忽略下一个输入(这就是为什么您必须按两次<Enter>)。


在Unix上,您肯定可以使用终端或常规文件(但不能在管道或套接字上)清除并继续读取EOF。 (但是,在Unix上,^Z是暂停,而在Windows上是EOF。)转换为bool会检查所有错误标志。 - Davis Herring
我只是想补充一下,std :: strinstream(input)>> output; 是不够的,因为 13ab 会被解析成 13 等等。您能否进一步说明在键盘输入下 std :: getline(std :: cin,string) 可能会出现哪些其他问题? - Daniel Duque
@DavisHerring 我也曾这样想过,但当我测试时,我无法重复使用 std::cinstd::cin.clear() 确实取消了 eofbit,但在调用 std::getline 时,eofbit 立即再次设置。你关于转换的观点是正确的,我会更改我的答案。 @DanielDuque 你是对的,我必须检查 istringstream 的 eofbit。实际上,除了 ^D/^Z 让它失败之外,我不知道还有其他方法。 - taminob
@Unterfliege:在某些实现中,显然你还需要在底层的FILE*上使用clearerr;我认为这是一个错误。 - Davis Herring
@DavisHerring 感谢您的更正,我已经编辑了我的回答。您是正确的,我必须使用 clearerr 才能重复使用 stdin。现在应该是正确的了。 - taminob
还有一个技术细节:如果getline超出了最大的string长度,它将失败。在大多数现代系统上极为罕见,但是可能会区分它,因为它仅设置failbit - Davis Herring

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接