std::stringstream作为参数

4

我对C++语言还有些陌生。我正在编写一个用于记录日志到文件的实用类。它的功能很好,但是现在我想通过使其更方便使用来增强它的功能(例如将stringstream传递给日志函数)。

这是我一直尝试但没有成功的内容。

定义:

void LogStream( std::stringstream i_Log ){ m_FileHandle << i_Log << std::endl; }

调用:

m_LogObject->LogStream( "MKLBSearchEngine::Search( " << x << ", " << i_Filter << " ) - No Results Found" );


3
什么无法正常运行?出现错误,或者行为不正确吗? - Martijn Courteaux
6个回答

11
你的解决方案存在几个问题。首先是你通过值传递了stringstream,而它不支持复制。你需要使用引用传递。其次,在调用点上,operator<<重载的返回值是ostream&而不是stringstream,由于stringstream不是ostream&的基类(它反过来),因此你无法用它来初始化stringstream(或stringstream&)。最后,没有接受stringstream作为右参数的operator<<,因此在LogStream函数中的语句无法工作。对用户来说,这将是有些麻烦的。大多数operator<<都是非成员函数,第一个参数是ostream&的非const引用,因此你无法以临时值作为左参数调用它们。(在你的示例调用中,当然,你忘记创建std::ostringstream;它不会编译,因为没有重载函数可以接受char const[]char const*作为左操作数。)
几乎所有这些问题都有解决方法,例如:
void LogStream( std::ostream& text )
{
    std::ostringstream& s = dynamic_cast<std::ostringstream&>(text);
    m_FileHandle << s.str() << std::endl;
}

处理所有问题,除了最后一个问题;最后一个问题需要由客户端处理,类似于:

m_LogObject->LogStream( std::ostringstream().flush() << "..." << x );

调用std::ostream::flush()返回流的非const引用,可以用于初始化进一步的std::ostream&。虽然你不能使用临时对象初始化一个非const引用,但是你可以在其上调用非const成员函数。

这对客户端代码的尴尬让我通常更喜欢一个更复杂的解决方案。我定义了一个特殊的LogStreamer类,类似于:

class LogStreamer
{
    boost::shared_ptr< std::ostream > m_collector;
    std::ostream* m_dest;

public:
    LogStreamer( std::ostream& dest )
        , m_collector( new std::ostringstream )
        , m_dest( &dest )
    {
    }
    ~LogStreamer()
    {
        if ( m_collector.unique() ) {
            *m_dest << m_collector->str() << std::endl;
        }
    }
    template <typename T>
    LogStreamer& operator<<( T const& value )
    {
        *m_collector << value;
        return *this;
    }
};

并且

LogStreamer LogStream() { return LogStreamer( m_FileHandle ); }

客户端代码可以这样编写:
m_LogObject->LogStream() << "..." << x;

在我的代码中:日志对象始终是单例的,调用是通过宏进行的,该宏将__FILE____LINE__传递给LogStream()函数,最终目标ostream是一个特殊的streambuf,由LogStream()调用一个名为“特殊函数”的函数来输出文件名、行号和时间戳,并在下一行输出时对所有其他行进行缩进。使用过滤streambuf的内容如下:
class LogFilter : public std::streambuf
{
    std::streambuf* m_finalDest;
    std::string m_currentHeader;
    bool m_isAtStartOfLine;
protected:
    virtual int overflow( int ch )
    {
        if ( m_isAtStartOfLine ) {
            m_finalDest->sputn( m_currentHeader.data(), m_currentHeader.size() );
            m_currentHeader = "    ";
        }
        m_isAtStartOfLine = (ch == '\n');
        return m_finalDest->sputc( ch );
    }
    virtual int sync()
    {
        return m_finalDest->sync();
    }

public:
    LogFilter( std::streambuf* dest )
        : m_finalDest( dest )
        , m_currentHeader( "" )
        , m_isAtStartOfLine( true )
    {
    }
    void startEntry( char const* filename, int lineNumber )
    {
        std::ostringstream header;
        header << now() << ": " << filename << " (" << lineNumber << "): ";
        m_currentHeader = header.str();
    }
};

(当然,函数now()返回带有时间戳的std::string。或者是一个struct tm,你已经为tm编写了一个<<。)

1

你的设计有问题。你不想把流作为参数传递,可以选择接受字符串或者让你的类行为像一个流(或两者都支持)。

如果你想让你的对象像一个流一样工作,你可以执行以下步骤:

m_LogObject << "what to log" << etc;

要做到这一点,只需覆盖 << 操作符即可。


1
为什么这比你批评的两个不相关的答案更相关?使用流作为参数有什么问题吗? - Duck
因为它不能按照你想要的方式使用。它不会起作用。你可以通过常量引用传递,然后必须在参数前面加上 stringstream(),这并不是很方便。 - Šimon Tóth
也许我读错了,但似乎错误在于OP对LogStream的调用弄错了。他传递的是字符串吗?可能没有理解如何创建stringstream?无论如何,如果向他展示正确创建stringstream并将其传递,那么问题就可以得到解决。 - Duck
@Duck 是的,他想要的是让他的类像流一样运作,而不是将其作为参数传递。 - Šimon Tóth
1
尽管我同意你对其他答案的看法,但在彻底检查了他错误使用函数后,你的答案也不是很相关,因为它是一个典型的“无论你的问题解决方案是什么,这里是一个完全不同的设计方法,只是为了绕过你的问题”的回答。 - Christian Rau

1

你的调用应该像这样

m_LogObject->LogStream( stringstream() << "MKLBSearchEngine::Search( " << x
 << ", " << i_Filter << " ) - No Results Found" );

由于您需要创建要传递给函数的stringstream对象。

这个调用意味着您已经有了所需的输出流,因此我建议您更改类设计以使用operator<<进行日志记录,除非它已经被重载。


1

你的函数调用不会起作用,因为"MKLBSearchEngine::Search("是const char*类型,它没有重载<<运算符。使用std::string("MKLBSearchEngine::Search( ")也不行,因为std::string也没有这样的运算符。你可以使用std::stringstream("MKLBSearchEngine::Search( ")来调用它,它将第一个参数转换为流,使得以下运算符在该流上工作。但正如其他人指出的那样,你必须将函数参数设置为const引用,因为流是不可复制的(即使这样做效率也很低)。此外,仅仅将std::stringstream写入文件并不能实现你想要的效果(如果它能工作的话),相反,你必须取出它的内容(底层的std::string)。总之,你的代码应该像这样:

void LogStream( const std::stringstream &i_Log ){ m_FileHandle << i_Log.str() << std::endl; }
...
m_LogObject->LogStream( std::stringstream("MKLBSearchEngine::Search( ") << x << ", " << i_Filter << " ) - No Results Found" );

但您也可以只使用 LogString(const std::string &),并让该函数的用户自行调用 stream.str()

0
你正在以值传递std::stringstream实例。 你想避免复制并通过引用(或指针)传递它。 例如:
void LogStream ( std::stringstream & i_Log ){ m_FileHandle << i_Log << std::endl; }

了解更多关于C++的参考资料


这有点不相关 :-D 请仔细阅读问题。 - Šimon Tóth

0

你不能通过值传递流对象(因为它们不可复制),所以你需要通过引用(并存储)来传递:

void LogStream(std::stringstream& i_Log){
    m_FileHandle << i_Log << std::endl;
}

可能不会按照你的期望进行操作(由于相当隐晦的原因,它可能打印i_Log的地址)。

如果你的意图是从字符串流中取出数据,那么这个方法可能符合你的要求:

i_Log.get( *m_FileHandle.rdbuf() );

这有点不相关 :-D 请仔细阅读问题。 - Šimon Tóth
@Christian 是的,而且它不起作用,因为你需要通过常量引用传递才能使其工作。即使如此,它也无法解决更大的问题。 - Šimon Tóth

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