【问题标题】:Correct way to declare/define custom cout-like object声明/定义自定义 cout-like 对象的正确方法
【发布时间】:2013-07-21 16:22:48
【问题描述】:

我创建了自己的 std::cout 类对象,该对象同时写入 std::cout 和日志文件。

我目前在头文件中这样定义它,但我收到未使用的变量警告。

头文件<MyLib/Log.h>

static LOut { };
static LOut lo;

template<typename T> inline LOut& operator<<(LOut& mLOut, const T& mValue)
{
    std::string str{toStr(mValue)};
    std::cout << str;
    getLogStream() << str;
    return mLOut;
}

用法:

#include <MyLib/Log.h>
...
lo << "hello!" << std::endl;

lo 应该是static 吗? lo应该是extern吗?

感谢解释声明cout-like 对象的正确方法并展示主要标准库实现如何做到这一点。


编辑:cout-like 对象,我的意思是一个全局变量,在包含相应的标头后始终可用。

【问题讨论】:

  • std::cout 通常只是某种std::ostream,具有一些特殊的逻辑来确保它足够早地初始化,并且永远不会被破坏;我知道至少有一个编译器使用特殊的扩展来实现这一点。但你通常不需要它;如果情况变得更糟,您可以使用单例,并写log() &lt;&lt; ...

标签: c++ c++11 static global-variables extern


【解决方案1】:

std::cout 简单声明如下:

namespace std {
    extern ostream cout;
}

是一个常规的全局变量;你可以自己做同样的事情。将变量的 extern 声明放在标题中;然后在源文件中定义相同的变量并将其链接到您的应用程序:

// mylog.h
extern MyLog mylog;

// mylog.cpp
MyLog mylog(someparams);

【讨论】:

  • 谢谢。我曾尝试将其声明为external,但忘记在源文件中定义变量。
【解决方案2】:

首先,我不太清楚你的意思是cout-like 对象? 也许是std::ostream

无论如何,通常的做法是使用过滤 流缓冲区。只需编写一个转发到日志文件的streambuf, 除了通常的地方,然后将它插入到任何你想要的地方 想要:

class LoggingOutputStreambuf : public std::streambuf
{
    std::streambuf* myDest;
    std::ofstreambuf myLogFile;
    std::ostream* myOwner;
protected:
    int overflow( int ch )
    {
        myLogFile.sputc( ch );  //  ignores errors...
        return myDest->sputc( ch );
    }
public:
    LoggingOutputStreambuf(
            std::streambuf* dest,
            std::string const& logfileName )
        : myDest( dest )
        , myLogFile( logfileName.c_str(), std::ios_base::out )
        , myOwner( nullptr )
    {
        if ( !myLogFile.is_open() ) {
            //  Some error handling...
        }
    }
    LoggingOutputStreambuf(
            std::ostream& dest,
            std::string const& logfileName )
        : LoggingOutputStreambuf( dest.rdbuf(), logfileName )
    {
        dest.rdbuf( this );
        myOwner = &dest;
    }
    ~LoggingOutputStreambuf()
    {
        if ( myOwner != nullptr ) {
            myOwner->rdbuf( myDest );
        }
    }
};

(这是 C++11,但修改它应该不难 C++03.)

要使用,你可以使用类似的东西:

LoggingOutputStreambuf logger( std::cout );
//   ...

将记录到std::cout 的所有输出,直到logger 熄灭 范围。

在实践中,您可能会使用比 filebuf 用于记录,因为您可能想要插入时间戳 在每行的开头,或在结尾有系统地刷新 每一行。 (过滤流缓冲可以解决这些问题 也是。)

【讨论】:

  • 谢谢,但我想要一个始终可用的全局变量,这就是我谈到 cout-like 对象的原因
  • 所以你关心的是生命周期。那还不清楚。在这种情况下,显而易见的解决方案是std::ostream&amp; logStream() { static std::ostream* theOneAndOnly = new MyStreamType; return *theOneAndOnly; } 语法略有变化:你必须写logStream() &lt;&lt; ...,而不是logStream &lt;&lt; ...,但仅此而已。 (并且您仍然可以在从logStream 返回的流中使用上面的LoggingOutputStreambuf;它仍然是将输出推送到两个不同目的地的最简单、最优雅的方式。)
【解决方案3】:

类似std::cout 的对象,同时写入std::cout 和日志文件

也许boost.iostreams 就足够了吗?

#include <iostream>
#include <fstream>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/tee.hpp>

namespace io = boost::iostreams;
int main()
{
    typedef io::tee_device<std::ostream, std::ofstream> teedev;
    typedef io::stream<teedev> LOut;
    std::ofstream outfile("test.txt");
    teedev logtee(std::cout, outfile);
    LOut mLOut(logtee);
    mLOut << "hello!" << std::endl;
}

【讨论】:

  • 我不想引入任何boost依赖,我想要一个像cout这样的全局变量
【解决方案4】:

简单地将输入值直接发送到 cout 对我来说不起作用,因为我想将标题和信息添加到日志输出中。

另外,我有我的静态调试类来包装日志流。

这是我设法做到这一点的方式,我希望它有用。我不知何故是 C++ 的新手,所以如果有什么问题,请随时告诉我:)

#include <iostream>
#include <sstream>
#include <ostream>

enum class DebugLevel {
    INFO,
    WARNING,
    ERROR
};

class Debug {

    public:

        /*  other Debug class methods/properties
            ...
        */

        // out stream object
        static struct OutStream {

                std::ostringstream stream;
                DebugLevel level = DebugLevel::INFO;

            public:

                // here you can add parameters to the object, every line log
                OutStream& operator()(DebugLevel l) {
                    level = l;
                    return *this;
                }

                // this overload receive the single values to append via <<
                template<typename T>
                OutStream& operator<<(T&& value) {
                    stream << value;
                    return *this;
                }

                // this overload intercept std::endl, to flush the stream and send all to std::cout
                OutStream& operator<<(std::ostream& (*os)(std::ostream&)) {

                    // here you can build the real console log line, add colors and infos, or even write out to a log file
                    std::cout << __TIME__ << " [" << (int)level <<  "] " << stream.str() << os;

                    stream.str(""); // reset the string stream
                    level = DebugLevel::INFO; // reset the level to info
                    return *this;
                }

        } Log;

};

Debug::OutStream Debug::Log; // need to be instantiaded only because I use a static Debug class

int main() {

    Debug::Log(DebugLevel::ERROR) << "Hello Log! " << 2 << " " << __FUNCTION__ << std::endl;

    Debug::Log << "Hello Log! " << 0xFA << std::endl; // NB: this way the debugLevel is default

    return 0;

}

【讨论】:

    【解决方案5】:

    在我的projects 之一中,我为std::cout 编写了包装器。

    看起来像这样:

    struct out_t {
        template<typename T>
        out_t&
        operator << (T&& x)  {
                std::cout << x;
                // log << x; 
                return *this;
        };
    };
    
    out_t out;
    
    out << 1;
    

    如需完整代码,请在 io.h 中查找 struct out

    【讨论】:

    • 这更接近我想要的 - 但是我想要一个可以在没有 operator() 的情况下使用的全局实例,就像 cout 一样。
    • 没有什么可以让你声明全局 out 对象。我已经编辑了代码来展示如何做到这一点。
    • @LeonidVolnitsky 如果我不使用 C++11,可以在您显示的代码 sn-p 中将 &amp;&amp; 替换为 &amp; 吗?
    猜你喜欢
    • 1970-01-01
    • 2015-03-25
    • 1970-01-01
    • 2022-06-17
    • 2020-01-20
    • 1970-01-01
    • 2018-05-14
    • 2015-06-17
    • 1970-01-01
    相关资源
    最近更新 更多