【问题标题】:Using << operator to write to both a file and cout使用 << 运算符写入文件和 cout
【发布时间】:2014-06-25 16:13:24
【问题描述】:

我想重载

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;
}

【问题讨论】:

  • 解释一下“但没能成功”是什么意思。
  • 看起来像是无意中的无限递归。相反,您想要的是带有streambufferostream,它可以透明地多路复用到任意数量的其他流。没有标准的。
  • 重载标准类型的操作符已经够糟糕的了,但是当那个操作符已经被重载了呢?
  • 非常感谢,我会检查其他主题。

标签: c++ file operator-overloading cout


【解决方案1】:

创建一个辅助类并重载运算符来处理流到两个流的问题。使用帮助类而不是尝试覆盖重载的operator&lt;&lt; 函数的标准库实现。

这应该可行:

#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;
}

【讨论】:

  • 在 std::flush 或 std::endl 上失败。说:'operator')。
  • @xamid,在我的桌面和ideone.com/a4QszP 上都为我工作。
  • 我在 Windows 7 上使用 MinGW-w64 编译器,c++11。
  • @xamid,我不知道如何帮助你。祝你好运。
  • 我在互联网上找到的所有方法都不适合我。大多数都有诸如刷新后停止写入文件的错误,有些没有编译。所以我尝试了 Boost (boost::iostreams::tee_device),它工作得很好。
【解决方案2】:

要实现完整的流接口,您应该构建一个流缓冲区和一个流:

#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 继承?
  • @JamesKanze 目的是增加一个缓冲区(处理所有格式化/未格式化)IO 并同步(刷新到)其他缓冲区。此外,多流可以作为 std::ostream& 传递而不会丢失功能。
  • 但是为什么要使用迭代器和容器接口呢?这与streambuf 有什么关系?如果您需要某种同步,以便两个目标同时进行物理写入,包含一个 std::vector&lt;char&gt; 用于缓冲区,然后在两个目标流缓冲区上执行 sync您将其转移给他们,应该可以解决问题。
  • 当我使用 ofstream 时,它只是在第一次刷新后停止写入文件(并且时间很短)。但它只有在我单独运行文件时才会这样做(不是通过 Eclipse)。你知道如何解决这个问题吗?我使用 MinGW-w64 和 Windows 7。
【解决方案3】:

如果你能够使用它,你会发现,不出所料,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 库。谢谢。
【解决方案4】:

您重载的函数的左侧为std::ostream&amp;,右侧为const std::string&amp;。这就是你调用函数的方式:

fl << "!!!Hello World!!!" << endl;

左侧完美匹配,但右侧不匹配。您传递的字符串不是std::string,而是char const* 类型的对象。即使存在从 char const*const std::string&amp; 的可行转换,您所做的自定义重载也不会被调用,因为实际上存在完美匹配其他地方

完美匹配的重载是命名空间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 中的重载,因为封闭命名空间中的重载在std 等外部命名空间之前被考虑。但这不仅不直观,还会引发其他问题。

  1. 您的函数需要返回类型 std::ostream&amp; 而不是 void 并具有 return os 语句,以便您可以链接 &lt;&lt; endl 表达式。

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

您的方法不是最好的方法,因此要获得更完整和更好的解决方案,请查看此线程中的其他答案和 cmets。

【讨论】:

  • 非常感谢您的详细解释,我现在清楚多了。
【解决方案5】:

它只是将值写入文本文件,因为您的 operator

操作符

你的 operator 具有相同的签名

【讨论】:

    【解决方案6】:

    通常的做法是使用过滤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}' main.cpp /ProofTool 没有匹配函数调用 'std::basic_filebuf::basic_filebuf(const string&)' 请求 '((LoggingStreambuf*)this)->LoggingStreambuf::myOwner' 中的成员 'rdbuf',它是指针类型 'std:: ostream* {aka std::basic_ostream*}'(也许你的意思是使用'->'?)请求'((LoggingStreambuf*)this)->LoggingStreambuf::myOwner'中的成员'rdbuf',其中是指针类型'std::ostream* {aka std::basic_ostream*}'(也许你打算使用'->'?)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-26
    • 1970-01-01
    相关资源
    最近更新 更多