【问题标题】:Why does `<< std::endl` not call the operator I want it to call?为什么 `<< std::endl` 不调用我希望它调用的运算符?
【发布时间】:2016-10-30 17:01:38
【问题描述】:

我正在寻找一种同时写入文件和控制台的解决方案。我找到了一个不错的解决方案here

由于我在 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;
}

然后所有std::endl 在控制台上的输出中都将被忽略,而它们在文件中正确显示。我的猜测是,当我使用std::endl 时,会调用ostream::operator&lt;&lt;,它将打印到文件而不是控制台。带有static_cast&lt;OutputAndConsole&amp;&gt; 的行是我尝试调用正确操作员的业余尝试,但控制台上仍然只出现\n 的换行符。

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

我怎样才能调用正确的?

PS:我知道我可以毫无问题地使用\n,但我仍然想知道这里发生了什么以及如何解决它。

【问题讨论】:

  • 如果您使用'\n' 结束一行,您将不会遇到此问题。你真的需要std::endl 做的额外的东西吗?
  • @PeteBecker 你是绝对正确的,我不需要它,但我仍然想了解出了什么问题以及如何解决它。
  • 停止。 ostream 已经有运营商来处理这些问题。让它完成它的工作并处理格式化的输出。您只需要将原始输出复制到另外两个流上。这是streambuf 班级的工作。只需正确实现它并将所有操作重定向到其他两个缓冲区:Dr.Dobb's articleanother link
  • 对于您的 PPS,您的过载优先级存在问题。
  • @tobi303:在链接的问题中,还有一个类似于给定链接的其他答案

标签: c++ operator-overloading


【解决方案1】:

std::endl 是一个函数,而不是一个字符串。 您的重载方法需要一个字符串进行重载,所以当您执行 &lt;&lt; std::endl

时,它不会被调用

您需要创建一个运算符,该运算符采用与std:endl 具有相同签名的函数来执行重载。

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

【讨论】:

  • 我不太明白,我的重载运算符采用const T&amp; 而不是字符串
  • @tobi303 是的,const T,不是字符串。尽管如此,当涉及到 std:endl 时,您仍需要给他完全相同的签名。我已经用运算符中使用的 std::endl 签名编辑了我的答案。
  • 好的,谢谢,让它工作,但我仍然不明白为什么模板没有涵盖这一点。我也试过T 而不是const T&amp; 然后我希望std::ostream&amp; (*)(std::ostream&amp;) 是一个有效的模板参数,不是吗?
  • @tobi303 const T& 在您的情况下是一个变量。操作员期待的是一个函数指针、一个委托、一个方法签名。当您声明一个函数以将另一个函数作为参数而不是变量时,您不能使用相同的语法,因为您必须说明该函数的签名是如何形成的。
  • 抱歉还是不明白。也许我只是有点太困惑了...如果我有一个带有参数T 的模板,我可以传递我想要的任何东西,只要模板格式正确。为什么我不能传递一个函数(或者为什么在这种情况下模板参数推导失败)?
【解决方案2】:

让我们尝试一些更简单的方法:

#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 &lt;&lt; std::endl; 时,编译器会从 CharTTraitsdecltype(std::cout) 实例化 std::endl

在您的代码中,编译器改为使用来自std::ofstreamCharTTraits 实例化模板,因为您的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&lt;&lt;。第二个表格由您提供。

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”。

【讨论】:

  • 我得考虑一下,但这听起来很好解释
【解决方案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;
}

【讨论】:

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

我建议不要通过流接口实现标准 I/O 流功能,而是流缓冲接口。一旦流被识别为 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

【讨论】:

  • 感谢分享。但是,我认为这会更好地回答一个询问如何同时写入控制台和文件的问题,而这个问题专门关于代码中的重载以及为什么它不适用于std::endl。一旦我有时间,我会提出一个新问题,你可以提出这个问题。
【解决方案5】:
struct OutputAndConsole : std::ofstream
{
  // ...
};

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

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

如果您尝试将模板函数传递给期望具有兼容签名的函数的函数,则可以将其转换为值。如果传递给采用Tconst T&amp; 的模板函数,它不会 转换为值,因为模板函数的名称代表可能值的整个宿主 .

因为std::endl 不是您自定义编写的operator&lt;&lt; 的有效参数,所以它会在别处查找。它找到std::ofstreamoperator&lt;&lt;,通过显式函数指针获取一个io操纵器函数。

那个有效,它可以将endl 转换为那个函数指针类型!所以,它兴高采烈地叫它。

要解决此问题,请将 operator&lt;&lt; 重载添加到 OutputAndConsole 以获取 io 操纵器函数指针。

最简单的方法是编写一个辅助函数:

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

然后是两个&lt;&lt; 重载:

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

【讨论】:

  • 第一个答案同时回答了为什么和如何,因此我会接受它(老实说,我不在乎谁是第一个或谁获得了代表:P)。实际上,将两个问题合二为一是我的错误……对不起,伙计们
猜你喜欢
  • 1970-01-01
  • 2019-07-28
  • 2021-05-16
  • 2014-08-20
  • 1970-01-01
  • 2014-12-19
  • 1970-01-01
  • 2021-07-03
  • 1970-01-01
相关资源
最近更新 更多