5

原始问题

我正在编写一个日志类,目标是能够做到这一点:

// thread one
Logger() << "Some string" << std::ios::hex << 45;
// thread two
Logger() << L"Some wide string" << std::endl;

目前我的 Logger 标头看起来像这样:

#pragma once;
#include <ostream>    
class Logger
{
public:
    Logger();
    ~Logger();

    std::ostream* out_stream;
};

template <typename T>
Logger& operator<< (Logger& logger, T thing) {
    *logger.out_stream << thing;
    return logger;
}

关于这个类的一些注意事项:

  1. 跨平台兼容性不是问题。
  2. 在 Logger.cpp 内部有一个单例类负责创建“真正的”ostream。
  3. Logger 构造函数和解构函数执行必要的单例锁定。

我有三个问题:

  • 如何使 operator<< 函数成为朋友或成员,以便我可以将 out_stream 设置为私有?
  • 如何使 operator<< 函数适用于操纵器?
  • 如何添加专业化,以便如果 T 是 WCHAR* 或 std::wstring,它将在将其传递给 out_stream 之前将其转换为 char* 或 std::string?(我可以进行转换。在我的情况下,丢失高 unicode 字符不是问题。)

答案中学到的东西总结:

  • 将模板放在朋友之前而不是之后。
  • std::ios::hex 不是操纵器。std::hex 是一个操纵器。

最终结果

#pragma once
#include <ostream>
#include <string>

std::string ConvertWstringToString(std::wstring wstr);

class Logger
{
public:
    Logger();
    ~Logger();

    template <typename T>
    Logger& operator<< (T data) {
        *out << data;
        return *this;
    }
    Logger& operator<< (std::wstring data) {
        return *this << ConvertWstringToString(data);
    }
    Logger& operator<< (const wchar_t* data) {
        std::wstring str(data);
        return *this << str;
    }

private:
    std::ostream* out;
};
4

4 回答 4

7

您可以使用朋友定义,它将在类的周围命名空间中定义运算符,并使其仅对运算符重载决议可见(不能使用 ::operator<<... 语法手动调用):

class Logger
{
public:
    Logger();
    ~Logger();

    std::ostream* out_stream;

    template <typename T>
    friend Logger& operator<< (Logger& logger, T thing) {
        *logger.out_stream << thing;
        return logger;
    }

    /* special treatment for std::wstring. just overload the operator! No need
     * to specialize it. */
    friend Logger& operator<< (Logger& logger, const std::wstring & wstr) {
        /* do something here */
    }

};

另一种方法是,为了保持你的代码不变并且只是让 operator<< 模板成为朋友,你可以将这一行添加到你的类定义中:

template <typename T>
friend Logger& operator<< (Logger& logger, T thing);

对于机械手问题,我将给你我前段时间写的代码:

#include <iostream>
#include <cstdlib>
using namespace std;

template<typename Char, typename Traits = char_traits<Char> >
struct logger{
    typedef std::basic_ostream<Char, Traits> ostream_type;
    typedef ostream_type& (*manip_type)(ostream_type&);
    logger(ostream_type& os):os(os){}
    logger &operator<<(manip_type pfn) {
        if(pfn == static_cast<manip_type>(std::endl)) {
            time_t t = time(0);
            os << " --- " << ctime(&t) << pfn; 
        } else
            os << pfn;
        return *this; 
    }
    template<typename T> 
    logger &operator<<(T const& t) { 
        os << t; 
        return *this;
    }
private:        
    ostream_type & os;
};

namespace { logger<char> clogged(cout); }
int main() { clogged << "something with log functionality" << std::endl; }

};

请注意,它是 std::hex ,但不是 std::ios::hex。后者用作setf流功能的操纵器标志。请注意,对于您的示例,不需要对操纵器进行特殊处理。仅需要对 std::endl 进行上述特殊处理,因为当使用 std::endl 时,我还会流式传输时间。

于 2008-11-14T16:29:55.053 回答
2

使用模板是正确的方法,但您只需要确保模板在文件(logger.h或任何您调用的文件)中,而不是在实现文件(logger.cpp)中。这将自动适用于operator <<使用std::ostream. 它还将自动与流操纵器对象一起使用——这些实际上只是带有std::ostream参数operator <<的函数,并且只是在ostream.

您可以operator <<按如下方式创建朋友功能:

template <typename T> friend Logger& operator<< (Logger& logger, T thing);

特化很容易 - 只需使用模板特化(同样,在头文件中):

template <typename T>
Logger& operator<< (Logger& logger, T thing) {
    *logger.out_stream << thing;
    return logger;
}

// Template specialization - the "template <>" part is necessary
template <>
Logger& operator<< (Logger& logger, const wchar_t *wstr)
{
  // convert wstr to an ANSI string and log it
}

template <>
Logger& operator<< (Logger& logger, const std::wstring & wstr)
{
  // convert wstr to an ANSI string and log it
}
于 2008-11-14T16:32:14.533 回答
2

无需友谊声明:

class Logger
{
public:
    Logger();
    ~Logger();

template <typename T>
inline Logger& Display(T thing)
{
   *out_stream << thing;
    return *this;
}
private:
    std::ostream* out_stream;
};

template <typename T>
Logger& operator<< (Logger& logger, T thing) 
{
    return logger.Display(thing);
}
于 2008-11-14T18:13:25.047 回答
0

为什么不使用 printf 方式并使用多参数方法(使用三个点...)。这仍然为您提供了很多格式化能力,并且不会像使用 << 时那样混乱。

例如:

Logger("This is my log msg %0X", 45);

等两秒钟,我会为您提取代码示例。

编辑:

void Logger(const char* format, ...)
{
    char szMsg[3000];

    va_list args;
    va_start( args, format );
    vsnprintf( szMsg, sizeof(szMsg) - 1, format, args );
    va_end(args);

    // code to print szMsg to a file or whatever here
}

如果你想把它用作一个类而不是一个独立的函数,你可以重载记录器运算符 (),它的工作原理是一样的

于 2008-11-14T16:26:55.043 回答