Как перегрузить оператор‹‹, который не принимает и не возвращает ostream

Исходный вопрос

Я пишу класс ведения журнала, цель которого - сделать это:

// 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 есть класс singleton, который заботится о создании «настоящего» ostream.
  3. Конструктор и деконструктор Logger выполняют необходимую блокировку синглтона.

У меня три проблемы:

  • Как мне сделать функцию operator‹‹ другом или членом, чтобы я мог установить out_stream как частный?
  • Как заставить функцию оператора‹‹ работать для манипуляторов?
  • Как я могу добавить специализацию, чтобы, если T является WCHAR* или std::wstring, он преобразовывал его в char* или std::string перед передачей в out_stream? (Я могу сделать преобразование. Потеря старших символов Юникода в моем случае не проблема.)

Краткое изложение того, что вы узнали из ответов:

  • Поместите шаблон ПЕРЕД другом, а не после.
  • 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;
};

person Jere.Jones    schedule 14.11.2008    source источник
comment
Ваш конечный результат неверен. специализации в области класса не разрешены :) вместо этого просто перегрузите их (опустите часть шаблона‹›). Это требуется в ответе Адама (который специализируется на области пространства имен), поскольку в противном случае (тогда нормальные операторные функции) больше не друзья.   -  person Johannes Schaub - litb    schedule 14.11.2008
comment
Интересно, что с ними там работало. Но, чтобы быть точным, я все равно удалил их. Спасибо!   -  person Jere.Jones    schedule 14.11.2008


Ответы (4)


Вы можете использовать определение друга, которое определит оператор в окружающем пространстве имен класса и сделает его видимым только для разрешения перегрузки оператора (не вызывается вручную с использованием синтаксиса ::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.

person Johannes Schaub - litb    schedule 14.11.2008
comment
Арх! Неудивительно, что я получил 204845 в журнале, когда я это сделал. Похоже, манипуляторы работают как есть. Спасибо! - person Jere.Jones; 14.11.2008

Использование шаблона — правильный способ сделать это, но вам просто нужно убедиться, что шаблон находится в файле header (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
}
person Adam Rosenfield    schedule 14.11.2008

Заявление о дружбе не требуется:

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);
}
person James Curran    schedule 14.11.2008

почему бы не сделать это способом 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
}

Если вы хотите использовать это как класс, а не как отдельную функцию, вы можете перегрузить оператор регистратора (), и он будет работать точно так же.

person Lodle    schedule 14.11.2008
comment
Самое отличное предложение! Есть две причины, по которым я хочу использовать формат ‹‹. Во-первых, я предпочитаю удобочитаемость цепочки ‹‹ вместе. Во-вторых, я хочу избежать создания большого количества буферов символов. - person Jere.Jones; 14.11.2008
comment
Существует ряд причин не использовать метод varargs в коде C++, в частности, он удаляет безопасность типов, создает тесную связь между вызывающим/вызываемым и неопределенное поведение UDT (даже несмотря на то, что они все еще разрешены). - person twokats; 14.11.2008
comment
Хорошо знать. Никогда не был поклонником ‹‹ в С++ - person Lodle; 15.11.2008
comment
Просто добавить, что это делает очень простой код по сравнению с выбранным ответом функции. - person Lodle; 15.11.2008