Custom manipulator for class

∥☆過路亽.° 提交于 2019-12-02 20:45:54

问题


I'm trying to write a stream manipulator with arguments. I have class with 3 int's CDate(Year, Month, Day). So I need to make manipulator date_format(const char*). e.g. :

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

Output will be :

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

Guess i need to use that

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

but i don't know how.


回答1:


You can use pword array for this. Every iostream in C++ has two arrays associated with it.

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

You can store you own data in it. To obtain an index, that refers to an empty element in all iword and pword arrays you should use function std::ios_base::xalloc(). It returns int that you can use as an unique index in *word. You should obtain that index once on the start-up, and than use it for all operations with *word.

Then programming your own manip will look like:

Manipulator function, that receives reference to ios_base object and pointer to the format string, simply stores that pointer in pword

iosObject.pword(index_from_xalloc) = formatString

Then overloaded operator << (>>) obtains format string from the iostream object in the same way. After that you just make a conversion referencing to the format.




回答2:


Manipulators with arguments don't work the same as those without arguments! The are just classes with a suitable output operator which instead of outputting a value manipulate the stream's state. To manipulate the stream state you'll probably set up a suitabe value stored with an iword() or a pword() associated with the dtream and used by the output operator.




回答3:


As chris suggested, I'd say that you should just use tm rather than your custom date class:

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

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

If you must implement come custom functionality that cannot be accomplished with get_time and put_time then you'd probably want to use a tm member as part of your class so you could just extend the functionality that is already there:

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

You could then use CDate as follows:

CDate a(2006, 5, 15);

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

EDIT:

After looking at your question again, I think that you have a misconception about how the insertion operator works, you cannot pass in both an object and a format: https://msdn.microsoft.com/en-us/library/1z2f6c2k.aspx

If you want to specify a format but still retain your CDate class, I'd again suggest the use of put_time:

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

If you again insist on writing your own format accepting function you'll need to create a helper class that can be constructed inline and support that with the insertion operator:

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

You could use this as follows:

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



回答4:


Like Dietmar said you can push the params into the iword() but i find this kind of solution to be tedious and annoying..

I prefer to just install lambda functions as a iomanips and use them to directly call into the various classes methods or otherwise build the custom print in place. For that purpose I have created a simple 100 line template installer/helper class for mainpulators which can add a lambda function as the manipulator of any class..

So for your CDate you might define you manip as

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

Then just direct the << op to use the manip installers print helper:

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

And your basically done..

The mainp installer code and a fully working example is in my blog post at: http://code-slim-jim.blogspot.jp/2015/04/creating-iomanip-for-class-easy-way.html

But to be nice.. here is the key part you want to put in a .h somewhere less all the printouts to demonstrate how it works:

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

Of course if you really do need the parameters then use the CustomManip::make_installer(...) function to get it done but for that you will have to visit the blog..



来源:https://stackoverflow.com/questions/29336267/custom-manipulator-for-class

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!