4

我目前有一个日志系统,它需要一个 char* 和 var args,然后使用它们来执行 printf。这适用于 C 风格的字符串,但我想要一些更干净的东西。目前,如果我使用 std::stringstream 我必须在日志系统之外创建 stringstream,然后将 char* 用于 stringstream 给出的字符串。它看起来像这样:

std::stringstream strStream;
strStream << "The value of x is: " << x;
logging::print( strStream.str().c_str() );

我想要的是将参数传递给函数,就好像我直接将它们与字符串流一起使用一样。从用户的角度来看,这看起来像这样:

logging::printStream("The value of x is: " << x);

或者可能像这样:

logging::printStream("The value of x is: ", x);

有没有办法以这样的方式使用日志记录,我可以使用字符串流而不必在日志记录系统的功能之外创建它?

这一点尤其重要,因为我打算创建一个宏来防止任何函数参数在发布版本中编译。如果我必须在它之外创建字符串流并将其传入,该宏将毫无用处。从技术上讲,我可以制作一个宏来执行我在这个问题中所说的字符串流,但这很混乱,因为我不会总是在此日志记录中使用 stringstreams,因此我将有一个用于标准日志记录的宏,以及一个用于使用 stringstreams 的不同宏,它在其中调用标准日志记录的宏。

4

5 回答 5

1

以下按预期工作(经过测试 - 用您的 logging::print 替换输出到 cerr):

#include<sstream>
#include<iostream>

class StringstreamLogger {
private:
    std::stringstream s;

public:
    StringstreamLogger () : s (std::ios_base::out) {
    }

    ~StringstreamLogger () {
    std::cerr << s.str () << std::endl; // logging::print (s.str ().c_str ());
    }

    std::stringstream& out () {
    return s;
    }
};

int main () {
    StringstreamLogger ().out () << "My log message";
    std::cerr << "Some later output to test for prompt logging (to ensure that logging is not delayed until the end of a block)" << std::endl;
}
于 2012-05-29T21:03:43.847 回答
0

你可以这样写:

print(static_cast<std::ostringstream&>(std::ostringstream() << "The value of x is: " << x).str().c_str());

也许这更符合你的喜好?

于 2012-05-29T15:54:48.563 回答
0

作为一个起点,你可以...

class Something {
public:
    std::ostream& error() const { ... code to return some std::ostream ... }
};

...

int main () {
    Something something;
    something.error() << "Frobnicate" << 4;
}

稍后您可以使用重载的流运算符添加自己的代理对象(我认为这仍然比编写您自己的派生流更容易std::ostream)。

当然,已经有大量的日志框架了,首先看看它们。

于 2012-05-29T16:11:38.247 回答
0

我想出了两个 HACK 解决方案,但它们应该可以工作。第一个不使用范围解析运算符并且更安全。第二个使用 noop int 变量来伪造范围。

#define logging_printStream(token) { std::stringstream o; o << token; logging::print(o.str().c_str()); }

namespace logging { int noop; }
#define printStream(token) noop = 0; { std::stringstream o; o << token; logging::print(o.str().c_str()); }

int main(int argc, const char** argv)
{
    int i = 1;

    // MORE SAFE
    logging_printStream(i)
    logging_printStream("is this magic? " << (i ? "yes" : "no"))

    // LESS SAFE
    logging::printStream(i)
    logging::printStream("is this magic? " << (i ? "yes" : "no"))
}

由于 17.6.4.3.2,我将 logging__printStream 更新为 logging_printStream

17.6.4.3.2 全局名称 [global.names]

某些名称和函数签名集始终保留给实现:

  • 每个包含双下划线 _或以下划线后跟大写字母 (2.12) 的名称都保留给实现以供任何使用。
  • 每个以下划线开头的名称都保留给实现用作全局名称空间中的名称。

由于 3.6.1,我独自离开了 main 的声明

3.6.1 主函数[basic.start.main]

一个程序应包含一个名为 main 的全局函数,它是程序的指定开始。独立环境中的程序是否需要定义主要功能是实现定义的。实现不应预定义主要功能。该功能不得重载。它应该有一个 int 类型的返回类型,否则它的类型是实现定义的。

于 2012-05-29T16:39:44.907 回答
0

重载输出流操作符的自定义类型怎么样?

struct logger
{
    std::stringstream ss_;

    template <typename T>
    friend logger& operator << (logger& l, T const& t) 
    {
    #ifdef DEBUG_MODE
        l.ss_ << t;
    #endif

        return l;
    }

    ~logger()
    {
    #ifdef DEBUG_MODE
        // output ss_.str().c_str() to your logger.
    #endif
    }
};

然后每当您需要记录输出时

logger log;
log << "Log this stuff: " << x << '\n';
于 2012-05-30T02:59:14.707 回答