2016-06-28 109 views
27

我一直在寻找解决方案来同时写入文件和控制台。我发现了一个很好的解决方案here为什么`<< std :: endl`不能调用我想调用的操作符?

由于我的工作预先C++ 11我不得不做出从亮度种族代码的微小变化在轨道:

#include <iostream> 
#include <fstream> 
#include <string> 

struct OutputAndConsole : std::ofstream 
{ 
    OutputAndConsole(const std::string& fileName) 
     : std::ofstream(fileName.c_str()) // constructor taking a string is C++11 
     , fileName(fileName) 
    {}; 

    const std::string fileName; 
}; 

template <typename T> 
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var) 
{ 
    std::cout << var;  
    static_cast<std::ofstream&>(strm) << var; 
    return strm; 
}; 

它可以很好地除了一件小事情需要我的困惑。如果我用这样的:

int main(){ 
    OutputAndConsole oac("testLog.dat"); 
    double x = 5.0; 
    oac << std::endl; 
    static_cast<OutputAndConsole&>(oac << "foo \n" << x << "foo").operator<<(std::endl); 
    oac << "foo" << std::endl; 
} 

那么所有的std::endl都在控制台上的输出,而他们在文件中正确显示忽视。我的猜测是,当我使用std::endl时,将调用ostream::operator<<,它将打印到文件而不是控制台。带有static_cast<OutputAndConsole&>的行是我试图调用正确的操作符的尝试,但仍然只有\n的换行符出现在控制台上。

为什么要调用std::endl错误的运算符?

我该怎么称呼正确的?

PS:我知道,我可以使用\n没有问题,但我仍想知道这到底是怎么回事,如何解决它。

+1

如果你使用'\ n'来结束一行,你将不会遇到这个问题。你真的需要'std :: endl'做的额外的东西吗? –

+1

@PeteBecker你是绝对正确的,我不需要它,但我仍然想知道什么是错的,以及如何解决它。 – user463035818

+3

停下。 'ostream'已经有处理这些的操作符。让它完成工作并处理格式化的输出。您只需要通过其他两个流复制原始输出。这是'streambuf'类的工作。只要正确实施,并将所有操作员重定向到其他两个缓冲区:[Dr.Dobb的文章](http://www.drdobbs.com/questions-answers/184403387),[另一个链接](http://www.cplusplus。 com/forum/general/64174 /#msg347154) –

回答

12
struct OutputAndConsole : std::ofstream 
{ 
    // ... 
}; 

template <typename T> 
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var); 

正如其他人所说的,std::endl是一个模板函数。模板功能不是值,它只是一个名称。

如果您试图将模板函数传递给期望兼容签名函数的函数,则可以将模板函数转换为值。这是转换为值,如果通过采取Tconst T&模板功能,因为模板函数的名称,代表可能值的整个主机

因为std::endl不是您定制的operator<<的有效参数,所以它看起来在别处。它找到std::ofstreamoperator<<,它通过显式函数指针接受一个io操纵函数。

这个工作,它可以将endl转换为该函数指针类型!所以,它愉快地称它。

要解决此问题,请将operator<<重载添加到OutputAndConsole,该重载需要使用io操纵函数指针。

要做到这一点,最简单的方法是写一个辅助函数:

template <class T> 
void output_to(OutputAndConsole& strm, const T& var) 
{ 
    std::cout << var;  
    static_cast<std::ofstream&>(strm) << var; 
}; 

那么双<<重载:

template<class T> 
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var) { 
    output_to(strm, var); 
    return strm; 
} 
OutputAndConsole& operator<<(OutputAndConsole& strm, std::ostream& (*var)(std::ostream&)) { 
    output_to(strm, var); 
    return strm; 
} 

这将导致std::endl模板找到匹配的<<

+1

首先回答这个问题,为什么和怎么回答,因此我会接受它(老实说,我不在乎谁是第一个或谁得到代表:P)。其实这是我的错误,在一个问题中有两个问题...对不起,人们 – user463035818

5

std::endl是一个函数,而不是一个字符串。 你重载方法需要一个字符串超载,所以它不是这一个谁被调用,当你做<< std::endl

你需要创建一个运营商谁需要谁拥有相同的签名std:endl做你的过载功能。

std::ostream& operator<<(std::ostream& (*f)(std::ostream&)) 
+0

我真的不明白,我的重载操作符采用'const T'而不是字符串 – user463035818

+0

@ tobi303是的,const T,而不是字符串。不过,当涉及到std:endl时,您需要给他完全相同的签名。我用运算符中使用的std :: endl的签名编辑了我的答案。 – Mekap

+0

好的感谢,使它的工作,但我还是不明白为什么这不被模板覆盖。我也尝试过'T'而不是'const T&',然后我期望'std :: ostream&(*)(std :: ostream&)'是一个有效的模板参数,不是吗? – user463035818

11

让我们尝试一些简单的:

#include <iostream> 

struct Foo { }; 

template <typename T> 
Foo& operator<<(Foo& foo, const T& var) 
{ 
    std::cout << var; 
    return foo; 
}; 

int main(){ 
    Foo foo; 
    foo << std::endl; 
} 

这并不编译:

a1.cpp: In function ‘int main()’: 
a1.cpp:14:9: error: no match for ‘operator<<’ (operand types are ‘Foo’ and ‘<unresolved overloaded function type>’) 
    foo << std::endl; 
     ^
a1.cpp:14:9: note: candidates are: 
a1.cpp:6:6: note: template<class T> Foo& operator<<(Foo&, const T&) 
Foo& operator<<(Foo& foo, const T& var) 
    ^
a1.cpp:6:6: note: template argument deduction/substitution failed: 
a1.cpp:14:17: note: couldn't deduce template parameter ‘T’ 

为什么?编译器试图告诉我们什么?

的std :: ENDL的定义可以在这里找到: http://en.cppreference.com/w/cpp/io/manip/endl

template< class CharT, class Traits > 
std::basic_ostream<CharT, Traits>& endl(std::basic_ostream<CharT, Traits>& os); 

所以std::endl不是一个变量。这是一个模板。更确切地说,是一个模板函数。在我的小代码中,编译器无法实例化模板。

当我们直接调用std::cout << std::endl;,编译器也会根据CharTstd::endldecltype(std::cout)Traits

在你的代码,编译器,而不是使用实例化和CharTTraitsstd::ofstream模板,因为你的OutputAndConsolestd::ofstream后裔。当std::cout尝试输出std::endl的错误实例时,它会失败。

PS:最后一段只是部分正确。当编写

oac << something; 

其中something具有类型T,

它可以,理论上,调用以下两种

std::ofstream& std::ofstream::operator<<(T) // or operator<<(const T&) 
// -- or -- 
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var); 

第一个定义是可能的,因为OutputAndConsolestd::ofstream继承的成员函数operator<< 。第二种形式由您提供。

something是一个变量时,它使用第二个定义。

something是模板时,它不能使用第二个定义,因为无法确定模板的参数。所以它使用第一个定义。因此,

oac << std::endl; // std::endl is a template 

相当于

static_cast<ofstream&>(oac) << std::endl; 

我们可以通过下面的代码中看到它:

#include <iostream> 

struct Foo : std::ofstream {}; 

template <typename T> 
Foo& operator<<(Foo& strm, const T& var) 
{ 
    std::cout << "X" << std::endl; 
    return strm; 
}; 

int main() { 
    Foo oac; 
    oac << std::endl; 
} 

此代码不会打印出 “X”。

+0

我将不得不考虑这个问题,但这听起来像是一个很好的解释 – user463035818

3

要让它工作,我会创造我自己的一套机械手:

struct ManipEndl {}; 
const ManipEndl manip_endl; 
OutputAndConsole& operator<<(OutputAndConsole& strm, const ManipEndl& foo) 
{ 
    std::cout << std::endl;  
    static_cast<std::ofstream&>(strm) << '\n'; // no need for std::endl here 
    return strm; 
}; 

int main(){ 
    OutputAndConsole oac("testLog.dat"); 
    double x = 5.0; 
    oac << manip_endl; 
    oac << x << manip_endl << "foo" << manip_endl; 
    oac << "foo" << manip_endl; 
} 
+0

我回答了为什么它会在我最初的答案中被调用。这个答案是如何解决它。请注意,'std :: endl'和换行符是两个不同的东西。 'x << std :: endl'等价于'x <<'\ n'<< std :: flush' – user31264

+0

哈哈不知道可以写两个不同的答案;) – user463035818

4

我建议不要实现通过流接口标准I/O流的功能,但streambuffer接口。只有当流被识别为std :: istream/std :: ostream时,定制的流接口通常会遇到麻烦(由于某些操作符,操纵器或函数引用了流)。

您可以使用:

#include <array> 
#include <iostream> 
#include <sstream> 
#include <vector> 

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

/// A (string) stream buffer for synchronizing writes into multiple attached buffers. 
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() 
    {} 

    template <typename...Buffers> 
    BasicMultiStreamBuffer(Buffers* ...buffers) { 
     std::array<buffer_type*, sizeof...(Buffers)> buffer_array{buffers...}; 
     m_buffers.reserve(buffer_array.size()); 
     for(auto b : buffer_array) { 
      if(b) 
       m_buffers.push_back(b); 
     } 
    } 

    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: 
    /// Attach a buffer. 
    void insert(buffer_type* buffer) { 
     if(buffer) m_buffers.push_back(buffer); 
    } 

    /// Synchronize and detach a buffer. 
    void erase(buffer_type* buffer) { 
     iterator pos = this->begin(); 
     for(; pos != this->end(); ++pos) { 
      if(*pos == buffer) { 
       char_type* p = this->pbase(); 
       std::streamsize n = this->pptr() - p; 
       if(n) 
        sync_buffer(*pos, p, n); 
       m_buffers.erase(pos); 
       break; 
      } 
     } 
    } 


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

    private: 
    int sync_buffer(buffer_type* buffer, char_type* p, std::streamsize n) { 
     int result = 0; 
     std::streamoff offset = 0; 
     while(offset < n) { 
      int k = buffer->sputn(p + offset, n - offset); 
      if(0 <= k) offset += k; 
      else { 
       result = -1; 
       break; 
      } 
      if(buffer->pubsync() == -1) 
       result = -1; 
     } 
     return result; 
    } 

    protected: 
    /// Synchronize with the attached buffers. 
    /// \ATTENTION If an attached buffer fails to synchronize, it gets detached. 
    virtual int sync() override { 
     int result = 0; 
     if(! m_buffers.empty()) { 
      char_type* p = this->pbase(); 
      std::streamsize n = this->pptr() - p; 
      if(n) { 
       iterator pos = m_buffers.begin(); 
       while(pos != m_buffers.end()) { 
        if(0 <= sync_buffer(*pos, p, n)) ++pos; 
        else { 
         pos = m_buffers.erase(pos); 
         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) 
    {} 

    template <typename ...Streams> 
    BasicMultiStream(Streams& ...streams) 
    : Base(&m_buffer), m_buffer(streams.rdbuf()...) 
    {} 

    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: 
    template <typename StreamIterator> 
    void insert(StreamIterator& first, StreamIterator& last) 
    { 
     while(first != last) 
      insert(*first++); 
    } 
    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; 


int main() { 
    MultiStream s(std::cout, std::cerr, std::clog); 
    s << "Hello World" << std::endl; 
    printf("[Three lines of output]\n"); 
} 

注意,将更改应用到的std :: basic_streambuf接口的唯一功能是virtual int sync() override

除了派生和初始化自定义流类之外,标准基本流类不提供任何接口。实际(虚拟)接口是标准流缓冲区basic_streambuf

+1

感谢分享。但是,我认为这样做会更好,因为它可以解决如何同时写入控制台和文件的问题,而这个问题专门针对代码中的重载问题,以及为什么它不适用于'std :: endl '。一旦我有时间,我会提出一个新问题,你可能会提出这个问题。 – user463035818

相关问题