Кастомный манипулятор для класса

Я пытаюсь написать потоковый манипулятор с аргументами. У меня есть класс с 3 INT CDate(год, месяц, день). Поэтому мне нужно сделать манипулятор date_format(const char*), например:

CDate a(2006, 5, 15);
cout <<"DATE IS : " << date_format("%Y-hello-%d-world-%m-something-%d%d") << a;

Выход будет:

DATE IS : 2006-hello-15-world-5-something-1515

Думаю, мне нужно это использовать

ios_base & dummy_date_format_manipulator ( ios_base & x )
{
    return x;
}

ios_base & ( * ( date_format ( const char * fmt ) ) )( ios_base & x )
{
    return dummy_date_format_manipulator;
}

но я не знаю как.

4 ответа

Решение

Ты можешь использовать pword массив для этого. У каждого iostream в C++ есть два массива, связанных с ним.

ios_base::iword - array of ints
ios_base::pword - array of void* pointers

Вы можете хранить свои собственные данные в нем. Чтобы получить индекс, который ссылается на пустой элемент во всех iword а также pword массивы вы должны использовать функцию std::ios_base::xalloc(), Возвращает int, который вы можете использовать как уникальный индекс в *word, Вы должны получить этот индекс один раз при запуске, а затем использовать его для всех операций с *word,

Тогда программирование вашего собственного манипулята будет выглядеть так:

Функция манипулятора, которая получает ссылку на ios_base объект и указатель на строку формата, просто сохраняет этот указатель в pword

iosObject.pword(index_from_xalloc) = formatString

Тогда перегруженный оператор << (>>) получает строку формата из объекта iostream таким же образом. После этого вы просто делаете преобразование, ссылаясь на формат.

Как сказал Дитмар, вы можете вставлять параметры в iword(), но я нахожу такое решение утомительным и раздражающим.

Я предпочитаю просто устанавливать лямбда-функции в виде iomanips и использовать их для прямого вызова методов различных классов или для создания собственной печати на месте. Для этой цели я создал простой 100-строчный шаблон установщика / вспомогательный класс для главных контроллеров, который может добавить лямбда-функцию в качестве манипулятора любого класса.

Таким образом, для вашего CDate вы можете определить свой манипул как

std::ostream& dummy_date_format_manipulator (std::ostream& os)
{
    CustomManip<CDate>::install(os,
                        [](std::ostream& oos, const CDate& a)
                        {
                            os << a.year() 
                                << "-hello-" 
                                << a.day()
                                << "-world-" 
                                << a.month() 
                                << "-something-"
                                << a.day() << a.day();
                        });
    return os;
}

Затем просто укажите << op для использования помощника по печати установщиков манипуляторов:

std::ostream& operator<<(std::ostream& os, const CDate& a)
{
    CustomManip<CDate>::print(os, a);
    return os;
}

И ваш в основном сделано..

Код установщика mainp и полностью рабочий пример находятся в моем блоге по адресу: http://code-slim-jim.blogspot.jp/2015/04/creating-iomanip-for-class-easy-way.html

Но чтобы быть милым... вот ключевая часть, которую вы хотите поместить в.h где-то меньше всех распечаток, чтобы продемонстрировать, как это работает:

//g++ -g --std=c++11 custom_class_manip.cpp

#include <iostream>
#include <ios>
#include <sstream>
#include <functional>

template <typename TYPE>
class CustomManip
{
private:
    typedef std::function<void(std::ostream&, const TYPE&)> ManipFunc;

    struct CustomManipHandle
    {
        ManipFunc func_;
    };

    static int handleIndex()
    {
        // the id for this Custommaniputors params
        // in the os_base parameter maps
        static int index = std::ios_base::xalloc();
        return index;
    }

public:
    static void install(std::ostream& os, ManipFunc func)
    {
        CustomManipHandle* handle =
            static_cast<CustomManipHandle*>(os.pword(handleIndex()));

        // check if its installed on this ostream
        if (handle == NULL)
        {
            // install it
            handle = new CustomManipHandle();
            os.pword(handleIndex()) = handle;

            // install the callback so we can destroy it
            os.register_callback (CustomManip<TYPE>::streamEvent,0);
        }

        handle->func_ = func;
    }

    static void uninstall(std::ios_base& os)
    {
        CustomManipHandle* handle =
            static_cast<CustomManipHandle*>(os.pword(handleIndex()));

        //delete the installed Custommanipulator handle
        if (handle != NULL)
        {
            os.pword(handleIndex()) = NULL;
            delete handle;
        }
    }

    static void streamEvent (std::ios::event ev,
                             std::ios_base& os,
                             int id)
    {
        switch (ev)
        {
            case os.erase_event:
                uninstall(os);
                break;
            case os.copyfmt_event:
            case os.imbue_event:
                break;
        }
    }

    static void print(std::ostream& os, const TYPE& data)
    {
        CustomManipHandle* handle
            = static_cast<CustomManipHandle*>(os.pword(handleIndex()));

        if (handle != NULL)
        {
            handle->func_(os, data);
            return;
        }

        data.printDefault(os);
    }
};

Конечно, если вам действительно нужны параметры, используйте для этого функцию CustomManip::make_installer(...), но для этого вам придется посетить блог..

Манипуляторы с аргументами не работают так же, как и без аргументов! Это просто классы с подходящим оператором вывода, который вместо вывода значения манипулирует состоянием потока. Чтобы манипулировать состоянием потока, вы, вероятно, установите значение Suitabe, сохраненное с iword() или pword() связан с dtream и используется оператором вывода.

Как предположил chris, я бы сказал, что вы должны просто использовать tm а не ваш пользовательский класс даты:

tm a{0, 0, 0, 15, 5, 2006 - 1900};

cout << put_time(&a, "%Y-hello-%d-world-%m-something-%d%d");

Если вы должны реализовать пользовательские функции, которые не могут быть выполнены с get_time а также put_time тогда вы, вероятно, захотите использовать tm член как часть вашего класса, так что вы можете просто расширить функциональность, которая уже есть:

class CDate{
    tm m_date;
public:
    CDate(int year, int month, int day): m_date{0, 0, 0, day, month, year - 1900}{}
    const tm& getDate() const{return m_date;}
};

ostream& operator<<(ostream& lhs, const CDate& rhs){
    auto date = rhs.getDate();
    return lhs << put_time(&a, "%Y-hello-%d-world-%m-something-%d%d");
}

Вы могли бы тогда использовать CDate следующее:

CDate a(2006, 5, 15);

cout << "DATE IS:" << a;

РЕДАКТИРОВАТЬ:

Посмотрев на ваш вопрос еще раз, я думаю, что у вас неправильное представление о том, как работает оператор вставки, вы не можете передать и объект, и формат: https://msdn.microsoft.com/en-us/library/1z2f6c2k.aspx

Если вы хотите указать формат, но сохраните свой CDate класс, я бы снова предложил использовать put_time:

cout << put_time(&a.getDate(), "%Y-hello-%d-world-%m-something-%d%d");

Если вы снова настаиваете на написании своей собственной функции приема формата, вам нужно создать вспомогательный класс, который может быть встроен и поддерживать его с помощью оператора вставки:

class put_CDate{
    const CDate* m_pCDate;
    const char* m_szFormat;
public:
    put_CDate(const CDate* pCDate, const char* szFormat) : m_pCDate(pCDate), m_szFormat(szFormat) {}
    const CDate* getPCDate() const { return m_pCDate; }
    const char* getSZFormat() const { return m_szFormat; }
};

ostream& operator<<(ostream& lhs, const put_CDate& rhs){
    return lhs << put_time(&rhs.getPCDate()->getDate(), rhs.getSZFormat());
}

Вы можете использовать это следующим образом:

cout << put_CDate(&a, "%Y-hello-%d-world-%m-something-%d%d") << endl;
Другие вопросы по тегам