【问题标题】:How to overload operator<< that doesn't take or return ostream如何重载不接受或不返回 ostream 的 operator<<
【发布时间】:2008-11-14 16:22:50
【问题描述】:

原始问题

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

// 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. Lo​​gger 构造函数和解构函数执行必要的单例锁定。

我有三个问题:

  • 如何使 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;
};

【问题讨论】:

  • 你的结果是错误的。不允许在类范围内进行特化 :) 只是重载它们(省略 template 部分)在 Adam 的回答中是必需的(在命名空间范围内专门化它们),否则(当时的普通运算符函数)不再是朋友。跨度>
  • 有趣的是,它在那里与他们合作。但是,为了正确起见,我还是删除了它们。谢谢!

标签: c++ templates operator-overloading


【解决方案1】:

您可以使用友元定义,它将在类的周围命名空间中定义运算符,并使其仅对运算符重载决议可见(不能使用 ::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 */
    }

};

另一种方法,为了保持你的代码原样,只是让操作符

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 时,我还会流式传输时间。

【讨论】:

  • 啊!难怪当我这样做时我在日志中得到 204845。看起来机械手按原样工作。谢谢!
【解决方案2】:

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

您可以将operator &lt;&lt;设为好友功能,如下所示:

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
}

【讨论】:

    【解决方案3】:

    不需要友谊声明:

    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);
    }
    

    【讨论】:

      【解决方案4】:

      为什么不使用 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
      }
      

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

      【讨论】:

      • 最优秀的建议!我想使用
      • 在 C++ 代码中不使用可变参数的原因有很多,特别是它消除了类型安全,在调用者/被调用者之间建立了紧密耦合,以及 UDT 的未定义行为(即使它们仍然是允许)。
      • 很高兴知道。从来都不是 的粉丝
      • 与选择的函数相比,只需添加它就可以生成非常简单的代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-24
      • 1970-01-01
      • 1970-01-01
      • 2022-08-04
      • 1970-01-01
      相关资源
      最近更新 更多