2016-06-28 107 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

相關問題