środa, 1 sierpnia 2012

(Bezsensowne) Przeładowania operatorów w C++

Przy pomocy przeładowania operatorów w C++ można robić bezsensowne rzeczy, które jednych zachwycą a u innych wywołają wrogość :) Jedną z takich rzeczy jest stworzenie obsługi list (np: zakupów, TODO, aliasów) czy też listy wad i zalet w taki "naturalny" sposób:
Aliases aliasList;
aliasList.add_for("number")
    * "one" 
    * "two" 
    * "three";

aliasList.add_for("moo")
    - "foo"
    - "bar"
    - "jaj";

aliasList.add_for("no_alias"); // identity

std::cout << "three stands for " << aliasList["three"] << '\n';
List list;

list.prons_and_cons_for("headphones X")
    + "price"
    + "quality"
    + "bass"
    - "too short cable"
    - "no volume control";

list.print_info_about("headphones X"); // or list.get_cons() etc
Implementacja polega na rozbiciu na dwie klasy.
Pierwsza, najważniejsza klasa polega na tym, że akumuluje argumenty (albo w moim przypadku - posiada referencje do kontenera drugiej klasy) w zadany sposób poprzez przeładowanie operatorów zwracających referencję - tak jak przeładowuje się zwykle operatory ">>" oraz "<<", przy czym trzeba również pamiętać o priorytetach i kierunku łączenia.
Druga klasa (Aliases, List w przykładach) zajmuje się tworzeniem pierwszej klasy i zawiera kontenery i właściwą logikę.

Jeśli nie jest to jeszcze jasne, to wystarczy spojrzeć na kod (drafty):
Tak to wygląda w pierwszym przypadku:
class AliasFor
{
public:
    AliasFor(const string &name, AliasList &target) : m_target(target), m_name(name) {
        m_target[name] = name; // just add identity
    }

    AliasFor& operator-(const char* alias) {
        m_target[alias] = m_name;
        return *this;
    }

    /// you can use each of this operators in your "list" instead of "-".
    AliasFor& operator*(const char* alias) { return (*this) - alias; }
    AliasFor& operator+(const char* alias) { return (*this) - alias; }
    AliasFor& operator&(const char* alias) { return (*this) - alias; }
    AliasFor& operator<<(const char* alias) { return (*this) - alias; }

private:
    map<string, string>& m_target;
    const string&        m_name;
};


class Aliases
{
public:
    // resolve alias or just return argument (to change if needed)
    std::string operator[](const string& alias){
        AliasList::iterator it = aliases.find(alias);
        if (it == aliases.end()) return alias;
        return it->second;
    }
    
    // creating new temporary object which holds reference to container
    // and name to assign aliases with
    AliasFor add_for(const string& name) {
        return AliasFor(name, aliases);
    }
private:
    AliasList aliases;
};
Przykład 2 jest troszkę bardziej skomplikowany - nie mniej główne założenia są takie same tylko jest trochę bardziej uogólnione:
template <typename T, typename TT>
class PronsAndCons
{
public:
    typedef std::pair<std::set<TT>, std::set<TT> > TPronsAndCons;
    typedef std::map<T, TPronsAndCons> TThings;

    class ListPronsAndCons {
    public:
        friend class PronsAndCons;
     
     ListPronsAndCons& operator-(const TT & con) {
        tab.first.insert(con);
        return *this;
     }

     ListPronsAndCons& operator+(const TT & pron) {
        tab.second.insert(pron);
        return *this;
     }
        
    private:
        ListPronsAndCons(TPronsAndCons& p) : tab(p) {}
        TPronsAndCons& tab;
    };

    ListPronsAndCons prons_and_cons_for(T what)
    {
        thing[what] = TPronsAndCons();
        return ListPronsAndCons(thing[what]);
    }

    void print_info_about(const std::string &what)
    {
        TPronsAndCons& tmp = thing[what];
        std::set<TT>::iterator it;
        std::cout << "Let's talk about " << what;
        std::cout << "\nProns are:";
        for (it = tmp.second.begin(); it != tmp.second.end(); ++it){
           std::cout << "\n\t+ " << *it;
        }

        std::cout << "\nCons are:";
        for (it = tmp.first.begin(); it != tmp.first.end(); ++it){
            std::cout << "\n\t- " << *it;
        } 
    }
private:
    TThings thing;
};

typedef PronsAndCons<std::string, std::string> ThingList;
Oczywiście można to rozbudować i dodając nowe typy można by napisać taką funkcjonalność:
Monster monster("joe");

monster.look()
   + head("one eye head")
   + torso("lion")
   + arms("with claws")
   + legs("frog");

monster.resistance()
   + fire(10)
   - frost(20);

monster.equip()
   + coins(100)
   + sword("of destiny")
   + thing("monster eye");
Czy jest to sensowne? Z pewnością nie ;) Ale można się pobawić, zobaczyć i zapomnieć :)

2 komentarze: