簡體   English   中英

為什么 `<< std::endl` 不調用我希望它調用的運算符?

[英]Why does `<< std::endl` not call the operator I want it to call?

我正在尋找一種同時寫入文件和控制台的解決方案。 我在這里找到了一個不錯的解決方案。

當我在 C++11 之前工作時,我不得不對 Lightness Races in Orbit 中的代碼進行一些小改動:

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

然后控制台上的 output 的所有std::endl都將被忽略,而它們在文件中正確顯示。 我的猜測是,當我使用std::endl時,會調用ostream::operator<<它將打印到文件而不是控制台。 帶有static_cast<OutputAndConsole&>的行是我嘗試調用正確運算符的業余嘗試,但控制台上仍然只有\n的換行符。

為什么std::endl調用了錯誤的運算符?

我怎樣才能調用正確的?

PS :我知道我可以毫無問題地使用\n ,但我仍然想知道這里發生了什么以及如何解決它。

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轉換為該函數指針類型! 所以,興高采烈地,它稱之為。

要解決此問題,請向OutputAndConsole io操縱器函數指針的OutputAndConsole添加一個operator<< overload。

最簡單的方法是編寫輔助函數:

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模板找到匹配的<<

讓我們嘗試一些更簡單的方法:

#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; 時,編譯器實例化std::endlCharTTraitsdecltype(std::cout)

在您的代碼中,編譯器使用來自std::ofstream CharTTraits來實例化模板,因為您的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”。

std::endl是一個函數,而不是一個字符串。 你的重載方法需要一個字符串來進行重載,所以當你執行<< std::endl時,它不會被調用

您需要創建一個運算符,該運算符接受與std:endl具有相同簽名的函數來執行重載。

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

我建議不要通過流接口實現標准的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

為了使它工作,我會創建自己的一組操縱器:

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

我有一個類似的問題,並通過讓我的operator<< function 成為這樣的朋友來解決它:

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>
    friend OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var)
    {
        std::cout << var;    
        std::ofstream::operator << (var); //*See note at end
        return strm;
    }
};

* 這是從派生的 class 方法/運算符調用基本 class 方法/運算符的首選語法。 雖然您的語法在大多數情況下都可以工作,但如果std::ofstream operator <<ofstream中聲明為純虛擬,它將失敗。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM