如何检查StringStream变量是否为空/ null?

63

这里是一个简单的问题。我一直在搜索但没有结果。

以下是更多信息:

stringstream report_string;

report_string << "some string here...";

我的代码本身有各种条件用于给report_string变量赋值。

我想检查它是否被赋过值。


好的,这里[可能]有两个不同的“事情”需要问 - 你尝试过什么? - user166390
请定义您的术语。提供一个使用此功能的示例程序将非常有帮助(其中包含您进行“检查null”的占位符)。 - Mankarse
你能澄清一下“变量为空/空值”的意思吗?你是想判断流中是否包含数据吗? - bobbymcr
抱歉,伙计们。我刚刚添加了更多的信息。 - cr8ivecodesmith
8个回答

68
myStream.rdbuf()->in_avail() 可以用来获取 stringstream 中待读取的字符数量,你可以使用它来检查你的 stringstream 是否为空。我假设你实际上并不想检查值为 null

例如,如果你想从 stringstream 中提取一个 int,然后查看是否有任何剩余的字符(即非数字),你可以检查 myStream.rdbuf()->in_avail() == 0

这是否与你尝试做的事情类似?我不确定是否有更好的方法,但我过去曾经这样做,它对我有效。

https://en.cppreference.com/w/cpp/io/basic_streambuf/in_avail

编辑:我看到你刚刚更新了你的问题。


1
正是我所需要的。这对我来说很好用,因为我在每次循环迭代时都会清除流,并且我只想看到它是否被赋值。谢谢! :) - cr8ivecodesmith
@Matt:与其在每次迭代时清除流,你可以在循环内部完美地声明它,并节省一些工作……和潜在的错误(比如使用continue并忘记清除它……) - Matthieu M.
1
我发现rdbug()->in_avail()总是返回0,就像GNU g++在网上的各个地方所描述的那样。我在http://www.cpp.re/forum/general/233925/找到了Cubbi的评论很有启发性:“它报告获取区域的大小(意思是,可以在不进行虚拟调用的情况下读取多少字节)……没有虚拟函数调用,写操作不可能触及流的读取端。”我的用例简单/有限,我不介意扔掉字符串缓冲区中的任何字符,所以我可以简单地测试`my_stream.readsome(&test_char, 1) == 0`以确认它为空。 - sage
另外,看一下我的_stream.tellp() == 0,那么my_stream.tellg()>=mystream.tellp()呢? 我真的没有看到get流指针比put流指针更大,但无论如何,这也避免了任何0转换不确定性,而且非常清晰,不会引起任何构造问题。 - Jan

16

这种方法效率高,应该也适用于输出字符串:

ostringstream report_string;

if (report_string.tellp() == 0) {
    // do something
}

9
应该是 if (report_string.tellp() == std::streampos(0)) - Oz Solomon
我喜欢这个,当然这取决于代码是否试图在你身上执行seekp()。此外,我认为OP的问题是关于在tellp() != 0时做某事。 - Alexis Wilke
@OzSolomon,您可以解释一下为什么使用 std::streampos(0) 比直接使用 0 好吗?我真的很感兴趣。 - Yurim
@Yurim 我现在不太记得我当时为什么发表了那个评论,但很可能是因为 std::fposstd::streampos 的实现类)没有标准正式要求具有接受 int 参数的构造函数。这意味着,根据您特定的标准库实现,0 可能会或可能不会隐式转换为 streampos - Oz Solomon
从标准中的“fpos要求”中,我们可以看到streampos通过“P p = o”隐式可转换,其中“P”是您的fpos类型(streampos-things),而“o”是您的streamoff类型(int-things)。在尝试猜测对未知编译器的支持之前,我会始终考虑YAGNI。 - c z

13

一个简单的检查是看流的字符串内容是否为空:

#include<assert.h>
#include<sstream>

int main(){
std::stringstream report_string;
report_string << ""; // an empty strin g

//emptiness check of stringstream
assert(report_string.str().empty());
}

18
str() 的问题在于它可能会创建一个巨大的内存块,只是为了检查它是否为空。 - Alexis Wilke

6

一种方法是检查内部字符串的大小并与零进行比较。请注意,这与AusCBlock建议的myStream.rdbuf()->in_avail()不同;in_avail()可以返回与流的实际大小不同的值(例如,如果内部缓冲区由多个非连续内存块表示)。特别地,in_avail()原则上可以在非空缓冲区中返回零(stringbuf规范可能进一步限制了这一点;我没有详细检查过)。


这看起来很有趣。我以后可能会需要它。谢谢! :) - cr8ivecodesmith

5
请使用eof()进行替换。
示例代码:
stringstream report_string;
if ( !(report_string.eof()) ) 
    cout << "report_string EMPTY! \n";

1
从我看到的情况来看,eof() 对于 std::stringstream 总是为假。文档中也没有看到任何相关说明。但即使它不为空,它的行为也是如此。 - Yuval
1
此答案中的代码出现问题,原因与 while (in.eof()) 经常出现问题相同:只有在之前的输入尝试在成功之前遇到 eof() 时,才会设置 eof()。因此,如果例如 report_string >> my_int 从输入中读取了一个数字,并且最后一位是流中的最后一件事,那么 eof() 将为 true。但是,如果您使用 char c = report_string.get(); 获取最终字符,则它将在文件结束时成功而不尝试执行任何操作,eof() 为 true。演示:这里 - Tony Delroy

5

通常情况下,使用...是合理且易读的。

report_string.str().empty()

...但这可能涉及动态分配和复制整个字符串到临时位置,最后又被抛弃。

如果性能很重要,另一个选择是...

report_string.peek() == decltype(report_string)::traits_type::eof()
  • 该方法用于查找流中尚未提取的字符,忽略已经成功解析/提取的输入

    • 这与测试report_string.str().empty()不同,后者仍然“看到”已提取的输入
  • 如果先前的解析使流处于未清除的fail状态,则无论是否有更多未提取的字符,此方法都将返回eof()


1
report_string.peek() == std::char_traits<char>::eof() 对我来说做得很好(即至少针对检查stringstream是否未修改的特殊情况)- 我认为 std::char_traits <char> ::eof() 可能与decltype(report_string)::traits_type ::eof()相同。如果检查失败,则我有兴趣将report_string.rdbuf()传递给std::cout以避免额外的复制,因此调用report_string.str()将使该优化变得无意义。(未经检查调用rdbuf具有副作用,并可能引发异常)。 - Apriori
@Apriori:如果report_string实例化为charstd::char_traits<char>::eof()将是相同的-你的方法更简单,但如果report_string的类型改变了,那么就需要额外的维护点-总的来说区别不大。关于rdbuf()可能会抛出异常的有趣见解-我以前并没有想到!干杯 - Tony Delroy

3
我知道这个问题非常旧并且已经有了答案,但是根据情况可能值得考虑另一种方法:
当您测试一个stringstream是否为空时,通常意味着要对其中的每个字符串或每行进行操作; 因此,您很可能会在stringstream上使用>> 运算符或 std::getline 方法...如果流是空的,它们将返回false,因此您可以编写以下代码: "最初的回答"
stringstream report_string;

foo(report_string)// some functions which may or may not write to report_string

string single_report;//string to read to

bool empty=true;//First assume it was empty
while(getline(report_string,single_report))//Alternatively use report_string>>single_report if you don't want entire lines
{
    empty=false;//...it wasn't empty
    bar(single_report);//Do whatever you want to do with each individual appended line 
}

if (empty)
{
    //... whatever you want to do if the stream was empty goes here
}

需要注意的是,这种方法假设您打算循环使用stringstream;如果不是这样,那么就不能使用这种方法。

最初的回答:

请注意,这种方法假定您计划循环使用stringstream;如果不是,则无法使用此方法。


0

还有其他的方法吗?

如果您将ostringstream作为可选类型,则可以在使用它之前检查它是否已被分配。

想象一个名为lazy<>的类,它在需要时懒惰地构造对象,然后我们可以这样做:

int main()
{
    using namespace std;

    auto oss1 = lazy<std::ostringstream>();
    auto oss2 = lazy<std::ostringstream>();

    use(oss1) << "Hello";

    if (oss1) cout << use(oss1).str() << endl;
    if (oss2) cout << use(oss2).str() << endl;

    if_used(oss1, [](auto& ss) { cout << ss.str() << endl; });
    if_used(oss2,
            [](auto& ss) { cout << ss.str() << endl; },
            [](auto& oss) { cout << "oss2 is not used" << endl; });

    use(oss2) << "Goodbye";
    if_used(oss2, [](auto& ss) { cout << ss.str() << endl; });

    return 0;
}

产生此输出:

Hello
Hello
oss2 is not used
Goodbye

优点:

  • 在不使用时,无需构建冗余的stringstream

  • 如果后续使用未使用的stringstream(通过const引用),则可选项提供异常。

下面是带有可定制构造函数的完整示例:

我已经使用了std::experimental来进行可选项处理,但您也可以轻松使用boost::optional

#include <iostream>
#include <experimental/optional>
#include <utility>
#include <type_traits>
#include <sstream>

using std::experimental::optional;

namespace detail {
    template<class T, class Constructor>
    struct lazy final
    {
        template<class Con , std::enable_if_t< not std::is_same<std::decay_t<Con>, lazy>::value > * = nullptr>
        lazy(Con&& con)
        : _constructor(std::forward<Con>(con))
        {}

        T& get() {
            if (not bool(_opt)) {
                _opt = _constructor();
            }
            return *_opt;
        }

        const T& get() const {
            return *_opt;
        }

        bool used() const {
            return bool(_opt);
        }

        operator bool() const {
            return used();
        }

    private:
        Constructor _constructor;
        optional<T> _opt;
    };

    template<class T>
    struct default_construct {
        T operator()() const { return T(); }
    };

    struct no_action {
        template<class T>
        void operator()(T&) const { }
    };
}


template<class T, class Constructor = detail::default_construct<T> >
auto lazy(Constructor&& con = detail::default_construct<T>())
{
    return detail::lazy<T, std::decay_t<Constructor>>(std::forward<Constructor>(con));
}

template<class T, class Constructor>
auto& use(detail::lazy<T, Constructor>& l)
{
    return l.get();
}

template<class T, class Constructor>
auto& use(const detail::lazy<T, Constructor>& l)
{
    return l.get();
}

template<class T, class Constructor, class F, class Else = detail::no_action>
void if_used(detail::lazy<T, Constructor>& l, F&& f, Else&& e = detail::no_action())
{
    if (l.used())
        f(l.get());
    else
        e(l);
}

template<class T, class Constructor, class F, class Else = detail::no_action>
void if_used(const detail::lazy<T, Constructor>& l, F&& f, Else&& e)
{
    if (l.used())
        f(l.get());
    else
        e(l);
}

int main()
{
    using namespace std;

    auto oss1 = lazy<std::ostringstream>();
    auto oss2 = lazy<std::ostringstream>();

    use(oss1) << "Hello";

    if (oss1) cout << use(oss1).str() << endl;
    if (oss2) cout << use(oss2).str() << endl;

    if_used(oss1, [](auto& ss) { cout << ss.str() << endl; });
    if_used(oss2,
            [](auto& ss) { cout << ss.str() << endl; },
            [](auto& oss) { cout << "oss2 is not used" << endl; });

    use(oss2) << "Goodbye";
    if_used(oss2, [](auto& ss) { cout << ss.str() << endl; });

    return 0;
}

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