C++中当重载operator<<运算符时,流作为参数

10

我正在尝试编写自己的日志类,并将其用作流:

logger L;
L << "whatever" << std::endl;

这是我开始的代码:

#include <iostream>

using namespace std;


class logger{
public:
    template <typename T>
    friend logger& operator <<(logger& log, const T& value);
};

template <typename T>
logger& operator <<(logger& log, T const & value) {
    // Here I'd output the values to a file and stdout, etc.
    cout << value;
    return log;
}

int main(int argc, char *argv[])
{
    logger L;
    L << "hello" << '\n' ; // This works
    L << "bye" << "alo" << endl; // This doesn't work
    return 0;
}

但是当我尝试编译时,遇到了一个错误,提示没有operator<<的定义(在使用std::endl时):

pruebaLog.cpp:31: error: no match for ‘operator<<’ in ‘operator<< [with T = char [4]](((logger&)((logger*)operator<< [with T = char [4]](((logger&)(& L)), ((const char (&)[4])"bye")))), ((const char (&)[4])"alo")) << std::endl’

我一直在尝试重载运算符<<来接受这种类型的流,但是它让我发疯了。我不知道该怎么做。例如,我一直在查看ostream头文件中std::endl的定义并编写了一个具有此头的函数:

logger& operator <<(logger& log, const basic_ostream<char,char_traits<char> >& (*s)(basic_ostream<char,char_traits<char> >&))

但是没有运气。我尝试过使用模板而不是直接使用char,也尝试过简单地使用“const ostream& os”,但都没有成功。
还有一件让我困扰的事情是,在错误输出中,operator<<的第一个参数会发生变化,有时它是指向指针的引用,有时看起来像是双重引用...

可能是[重复问题](https://dev59.com/XnNA5IYBdhLWcg3wAItP):在重载operator<<时,std :: endl的类型未知。 - sth
4个回答

9

endl 是一个奇怪的东西。它不是一个常量值,而是一个函数。你需要特殊的覆盖来处理 endl 的应用:

logger& operator<< (logger& log, ostream& (*pf) (ostream&))
{
  cout << pf;
  return log;
}

这个函数接受一个以ostream引用为参数并返回ostream引用的函数作为插入项。这就是endl的含义。

注:针对FranticPedantic的有趣问题“为什么编译器不能自动推导?”,原因在于进一步深入探究,endl实际上是一个模板函数。它的定义如下:

template <class charT, class traits>
  basic_ostream<charT,traits>& endl ( basic_ostream<charT,traits>& os );

也就是说,它可以将任何类型的ostream作为其输入和输出。所以问题不在于编译器无法推断T const&可能是一个函数指针,而是它无法确定你要传递哪个endl。问题中呈现的模板化的operator<<版本会接受指向任何函数的指针作为其第二个参数,但同时,endl模板表示了一个无限的潜在函数集合,因此编译器在这里无法做出有意义的操作。
提供特殊重载的operator<<,使其第二个参数匹配endl模板的特定实例,可以解决调用问题。

谢谢!问题解决了。现在我看到你的解决方案,发现我的定义basic_ostream<char,char_traits<char> >& (*s)(basic_ostream<char,char_traits<char> >&)是一个通用版本的ostream,因为它被定义为“const”,所以无法工作。一旦我去掉了const,它就可以工作了。 - José Tomás Tocino
对我来说,真正的问题仍然是,为什么编译器不能将 T 推断为函数指针并绑定到它? - Bill Prin

5

endl是一个IO操作符,它是一个函数对象,接受一个流的引用,对其进行某些操作,并通过引用返回该流。 cout << endl等同于cout << '\n' << flush,其中flush是一个刷新输出缓冲区的操作符。

在您的类中,您只需要编写此运算符的重载:

logger& operator<<(logger&(*function)(logger&)) {
    return function(*this);
}

其中logger&(*)(logger&)是接受并返回引用logger类型的函数的类型。要编写自己的操作器,只需编写一个符合该签名的函数,并让它在流上执行某些操作:

logger& newline(logger& L) {
    return L << '\n';
}

这比被接受的答案更复杂,但仍然是一种有趣的方法。 - Bill Prin
@FranticPedantic:其实是同一个硬币的两面。 - Jon Purdy

0
在C++中,流缓冲区封装了底层的I/O机制。流本身仅封装了字符串转换和I/O方向。
因此,您应该使用预定义的流类之一,而不是自己创建流类。如果您有一个新的目标要将I/O发送到(比如系统日志),您应该创建自己的流缓冲区(派生自std::streambuf)。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接