使用<<运算符将内容同时输出到文件和控制台

5
我想重载<<运算符,使其能够将取到的值写入文件并输出到控制台。我尝试使用以下代码来实现,但是没有成功,它只把值写入了文本文件。希望能够得到帮助。谢谢。
void operator<<(std::ostream& os, const string& str)
{
    std::cout << str;
    os << str;
}

int main() {
    ofstream fl;
    fl.open("test.txt");
    fl << "!!!Hello World!!!" << endl;
    return 0;
}

请解释一下你所说的“但是没有成功”。 - R Sahu
8
看起来是无意中发生了无限递归。相反,你要做的是使用一个具有透明复用到任意数量其他流的 streambufferostream 。没有标准的这样的流。 - Deduplicator
2
几乎是 https://dev59.com/inHYa4cB1Zd3GeqPIzht 的重复。 - Jerry Coffin
1
为标准类型重载运算符已经够糟糕的了,但如果该运算符已经被重载过了呢? - chris
非常感谢,我会去查看其他主题的。 - serhatg
6个回答

9
创建一个辅助类并重载运算符,以处理向两个流进行输出。使用辅助类而不是尝试覆盖重载的 operator<< 函数的标准库实现。
应该这样做:
#include <iostream>
#include <fstream>

struct MyStreamingHelper
{
    MyStreamingHelper(std::ostream& out1,
                      std::ostream& out2) : out1_(out1), out2_(out2) {}
    std::ostream& out1_;
    std::ostream& out2_;
};

template <typename T>
MyStreamingHelper& operator<<(MyStreamingHelper& h, T const& t)
{
   h.out1_ << t;
   h.out2_ << t;
   return h;
}

MyStreamingHelper& operator<<(MyStreamingHelper& h, std::ostream&(*f)(std::ostream&))
{
   h.out1_ << f;
   h.out2_ << f;
   return h;
}

int main()
{
   std::ofstream fl;
   fl.open("test.txt");
   MyStreamingHelper h(fl, std::cout);
   h << "!!!Hello World!!!" << std::endl;
   return 0;
}

1
在 std::flush 或 std::endl 上失败.. 报错:no match for 'operator<<' (operand types are 'xamid::helper::MyStreamingHelper' and '<unresolved overloaded function type>'). - xamid
@xamid,在我的桌面上以及 https://ideone.com/a4QszP 上都适用。 - R Sahu
我正在Windows 7上使用MinGW-w64编译器,c++11。 - xamid
@xamid,我不知道如何在那方面帮助你。祝你好运。 - R Sahu
我在互联网上找到的所有方法都不适用于我。大多数存在错误,例如刷新后停止写入文件,有些无法编译。因此,我尝试了 Boost (boost::iostreams::tee_device) ,它完全可行。 - xamid

4

如果您能使用它,您会发现Boost库已经为您完成了大部分艰苦的工作,这并不奇怪。

#include <iostream>
#include <fstream>
#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/stream.hpp>

typedef boost::iostreams::tee_device<std::ostream, std::ostream> teedev;
typedef boost::iostreams::stream<teedev, std::char_traits<typename std::ostream::char_type>, std::allocator< typename std::ostream::char_type > > tee_stream;

int main(int argc, char* argv[])
{
    std::ofstream of;
    of.open( "test.txt" );

    teedev td( of, std::cout );
    tee_stream ts(td);

    ts << "!!!Hello World!!!" << std::endl;

    return 0;
}

啊,当然,我没想到要检查boost库。谢谢。 - serhatg

3
实现完整的流接口,您应该构建一个流缓冲区和一个流:
#include <ostream>
#include <sstream>
#include <streambuf>
#include <vector>

// BasicMultiStreamBuffer
// ============================================================================

template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> >
class BasicMultiStreamBuffer : public std::basic_stringbuf<Char, Traits, Allocator>
{
    // Types
    // =====

    private:
    typedef typename std::basic_stringbuf<Char, Traits> Base;

    public:
    typedef typename std::basic_streambuf<Char, Traits> buffer_type;
    typedef typename buffer_type::char_type char_type;
    typedef typename buffer_type::traits_type traits_type;
    typedef typename buffer_type::int_type int_type;
    typedef typename buffer_type::pos_type pos_type;
    typedef typename buffer_type::off_type off_type;

    private:
    typedef typename std::vector<buffer_type*> container_type;

    public:
    typedef typename container_type::size_type size_type;
    typedef typename container_type::value_type value_type;
    typedef typename container_type::reference reference;
    typedef typename container_type::const_reference const_reference;
    typedef typename container_type::iterator iterator;
    typedef typename container_type::const_iterator const_iterator;


    // Construction/Destructiion
    // =========================

    public:
    BasicMultiStreamBuffer()
    {}

    BasicMultiStreamBuffer(buffer_type* a) {
        if(a) {
            m_buffers.reserve(1);
            m_buffers.push_back(a);
        }
    }

    template <typename Iterator>
    BasicMultiStreamBuffer(Iterator first, Iterator last)
    :   m_buffers(first, last)
    {}

    ~BasicMultiStreamBuffer() {
        sync();
    }


    private:
    BasicMultiStreamBuffer(BasicMultiStreamBuffer const&); // No Copy.
    BasicMultiStreamBuffer& operator=(BasicMultiStreamBuffer const&); // No Copy.


    // Capacity
    // ========

    public:
    bool empty() const { return m_buffers.empty(); }
    size_type size() const { return m_buffers.size(); }


    // Iterator
    // ========

    public:
    iterator begin() { return m_buffers.begin(); }
    const_iterator begin() const { return m_buffers.end(); }
    iterator end() { return m_buffers.end(); }
    const_iterator end() const { return m_buffers.end(); }


    // Modifiers
    // =========

    public:
    void insert(buffer_type* buffer) {
        if(buffer) m_buffers.push_back(buffer);
    }

    void erase(buffer_type* buffer) {
        iterator pos = this->begin();
        for( ; pos != this->end(); ++pos) {
            if(*pos == buffer) {
                m_buffers.erase(pos);
                break;
            }
        }
    }


    // Synchronization
    // ===============

    protected:
    virtual int sync() {
        int result = 0;
        if( ! m_buffers.empty()) {
            char_type* p = this->pbase();
            std::streamsize n = this->pptr() - p;
            if(n) {
                const_iterator pos = m_buffers.begin();
                for( ; pos != m_buffers.end(); ++pos) {
                    std::streamoff offset = 0;
                    while(offset < n) {
                        int k = (*pos)->sputn(p + offset, n - offset);
                        if(0 <= k) offset += k;
                        else {
                            result = -1;
                            break;
                        }
                    }
                    if((*pos)->pubsync() == -1) result = -1;
                }
                this->setp(this->pbase(), this->epptr());
            }
        }
        if(Base::sync() == -1) result = -1;
        return result;
    }

    private:
    container_type m_buffers;
};

typedef BasicMultiStreamBuffer<char> OStreamBuffers;


// BasicMultiStream
// ============================================================================

template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> >
class BasicMultiStream : public std::basic_ostream<Char, Traits>
{
    // Types
    // =====

    private:
    typedef std::basic_ostream<Char, Traits> Base;

    public:
    typedef BasicMultiStreamBuffer<Char, Traits, Allocator> multi_buffer;
    typedef std::basic_ostream<Char, Traits> stream_type;

    typedef typename multi_buffer::buffer_type buffer_type;
    typedef typename multi_buffer::char_type char_type;
    typedef typename multi_buffer::traits_type traits_type;
    typedef typename multi_buffer::int_type int_type;
    typedef typename multi_buffer::pos_type pos_type;
    typedef typename multi_buffer::off_type off_type;

    typedef typename multi_buffer::size_type size_type;
    typedef typename multi_buffer::value_type value_type;
    typedef typename multi_buffer::reference reference;
    typedef typename multi_buffer::const_reference const_reference;
    typedef typename multi_buffer::iterator iterator;
    typedef typename multi_buffer::const_iterator const_iterator;


    // Construction
    // ============

    public:
    BasicMultiStream()
    :   Base(&m_buffer)
    {}
    BasicMultiStream(stream_type& stream)
    :   Base(&m_buffer), m_buffer(stream.rdbuf())
    {}

    template <typename StreamIterator>
    BasicMultiStream(StreamIterator& first, StreamIterator& last)
    :   Base(&m_buffer)
    {
        while(first != last) insert(*first++);
    }

    private:
    BasicMultiStream(const BasicMultiStream&); // No copy.
    const BasicMultiStream& operator = (const BasicMultiStream&); // No copy.

    // Capacity
    // ========

    public:
    bool empty() const { return m_buffer.empty(); }
    size_type size() const { return m_buffer.size(); }


    // Iterator
    // ========

    public:
    iterator begin() { return m_buffer.begin(); }
    const_iterator begin() const { return m_buffer.end(); }
    iterator end() { return m_buffer.end(); }
    const_iterator end() const { return m_buffer.end(); }


    // Modifiers
    // =========

    public:
    void insert(stream_type& stream) { m_buffer.insert(stream.rdbuf()); }
    void erase(stream_type& stream) { m_buffer.erase(stream.rdbuf()); }

    private:
    multi_buffer m_buffer;
};

typedef BasicMultiStream<char> MultiStream;


// Test
// =============================================================================

#include <iostream>
int main() {
    MultiStream out;
    out.insert(std::cout);
    out.insert(std::clog);
    out << "Hello\n";
}

在这里,输出被缓存在字符串流缓冲区中,并同步到目标流。


为什么这么复杂?而且,既然可以直接从streambuf继承,为什么还要从stringbuf继承呢? - James Kanze
@JamesKanze 的意图是增加一个额外的缓冲区(处理所有格式化/未格式化的)IO,并同步(刷新到)其他缓冲区。此外,多流可以作为 std::ostream& 传递而不会失去功能。 - user2249683
但是迭代器和容器接口有什么关系呢?这与streambuf有什么关系?如果您需要某种形式的同步,以便两个目标同时进行物理写入,那么将std::vector<char>作为缓冲区进行_包含_,然后在将其传输到它们时对两个目标streambuf执行sync应该就可以解决问题了。 - James Kanze
当我使用 ofstream 时,它在第一次 flush(和短时间后)之后就停止向其文件写入。但只有在我单独运行文件时才会出现这种情况(不是通过 Eclipse)。您知道如何解决这个问题吗?我使用 MinGW-w64 和 Windows 7。 - xamid

1
你重载的函数接受一个左操作数 std::ostream& 和一个右操作数 const std::string&。这是你调用该函数的方式:
fl << "!!!Hello World!!!" << endl;
左边完全匹配,但右边不匹配。您传递的字符串不是std::string,而是char const*类型的对象。尽管从char const*const std::string&有可行的转换,但您制作的自定义重载没有被调用,因为实际上在其他地方存在一个完美匹配。

完美匹配的重载是在命名空间std中找到的重载,其签名为:
namespace std { // Simplification:
    std::ostream& operator<<(std::ostream& os, char const* str);
}

这是更好的匹配,因为右手参数的问题不需要进行转换。另一个原因是因为有一种称为ADL或Argument Dependent Lookup的东西。通常情况下,您必须从其他命名空间(如std)外部显式限定名称或函数,但是在涉及ADL时,如果编译器可以找到一个来自同一命名空间的用户定义类型的函数,调用该函数将不需要来自该命名空间外部的显式限定。因此,上述内容等效于:

std::operator<<(f1, "!!!Hello World!!!") << std::endl;
您在使用std::getline()时可以看到这一点。即使我们不使用using namespace stdusing std::getline,以下代码仍然是良好的形式:
getline(std::cin, line);

由于std :: cin与函数std :: getline()在相同的命名空间中,因此您无需在函数调用中附加std ::
因此,为了调用您的重载版本,必须有更好的匹配。您可以通过显式创建std :: string来强制执行此操作:
fl << std::string("!!!Hello World!!!") << endl;
由于嵌套命名空间中的重载比外部命名空间中的重载更优先考虑,因此调用了您的重载而不是std命名空间中的重载。但这不仅是非直观的,还会导致其他问题。
  1. 您的函数需要返回类型为std :: ostream&而不是void,并且需要有一个return os语句,以便可以链接<< endl 表达式。

  2. 在函数内部,您正在os << str行上执行无限递归。有许多解决此问题的方法,最简单的方法是执行os << str.c_str(),以便调用命名空间std中的char const *重载。

您的方法不是完成此操作的最佳方法,因此要获取更完整和更好的解决方案,请查看此线程中的其他答案和评论。


非常感谢您详细的解释,现在我理解得更清楚了。 - serhatg

0
通常的做法是使用过滤streambuf,它会转发到两个目标streambuf。以下类似的代码应该可以解决问题:
class LoggingStreambuf : public std::streambuf
{
    std::streambuf* myPrinciple;
    std::ostream*   myOwner;
    std::filebuf    myLogging;
protected:
    int overflow( int ch ) override
    {
        myLogging->sputc( ch );
        return myPrinciple->sputc( ch );
    }
public:
    LoggingStreambuf( std::streambuf* principal, std::string const& logFileName )
        : myPrinciple( principal )
        , myOwner( nullptr )
        , myLogging( logFileName )
    {
    }

    LoggingStreambuf( std::ostream& principal, std::string const& logFileName )
        : myPrinciple( principal.rdbuf() )
        , myOwner( &principal )
        , myLogging( logFileName )
    {
        myOwner.rdbuf( this );
    }

    ~LoggingStreambuf()
    {
        if ( myOwner != nullptr ) {
            myOwner.rdbuf( myPrinciple );
        }
    }
};

这段代码假设输出文件是一个日志文件,一个次要的输出,错误应该被忽略。 其他错误处理策略也可以实现,当然,你也可以提供两个任意的std::streambuf*,并创建一个新的std::ostream通过自定义的streambuf实例输出。

要使用这个类,按照它的写法:

LoggingStreambuf logger( std::cout, "logfile.txt" );
//  And output to std::cout as usual...

更一般地说,当你想要在流的数据源或接收端执行特殊操作时,你需要实现一个新的 std::streambuf,因为这是处理流的数据源和接收端的类。

无法编译:'->' 的基本操作数具有非指针类型 'std::filebuf {aka std::basic_filebuf<char>}' main.cpp /ProofTool 没有匹配的函数调用 'std::basic_filebuf<char>::basic_filebuf(const string&)' 请求成员 'rdbuf' 在 '((LoggingStreambuf*)this)->LoggingStreambuf::myOwner' 中,它是指针类型 'std::ostream* {aka std::basic_ostream<char>}'(也许你想使用 '->' ?) 请求成员 'rdbuf' 在 '((LoggingStreambuf)this)->LoggingStreambuf::myOwner' 中,它是指针类型 'std::ostream* {aka std::basic_ostream<char>*}'(也许你想使用 '->' ?) - xamid

0

它只是将值写入文本文件,因为您的operator<<方法没有被调用。

operator<<应该返回流的引用(ostream&),否则<< str << endl;将无法工作。

您的operator<<的另一个问题是它可能会创建一个无限循环,因为os << str;与fl << "string";具有相同的签名。


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