wypełnianie kontenera przeciążonym operatorem <<

0

Problem jest taki. mamy funkcję

bool Sprawdz(Kolekcja &d);

Kolekcja gromadzi wskaźniki do obiektów kilku klas : Dane1, Dane2, Dane3 itd., do około 20; przy czym wszystkie one pochodzą od klasy bazowej Baza. Użytkownikowi byłoby wygodnie stosować na przykład taki zapis

if (Sprawdz( Dane3(...) << Dane6(...) << Dane1(...))) {
    /*rób coś*/
    }

Innymi słowy chodzi o zainicjowanie kolekcji przekazywanej do funkcji obiektami o wspólnym interfejsie w miejscu jej użycia (*). Obiekty w kolekcji mogą zniknąć po zakończeniu wywołania Sprawdź, to nie przeszkadza.
W przypadku mojego podejścia problem sprowadza się do właściwego przeciążenia operatorów <<. Najlepiej żeby były tylko dwa, coś w stylu:

Kolekcja operator<<(Baza , Baza);    //definicje z dokładnością do wskaźnika/referencji
Kolekcja operator<<(Kolekcja, Baza);

Pierwszy żeby to wszystko zacząć, a drugi żeby dało radę wykonać kaskadę.
Jak widać strasznie mącę, żeby wypowiedzieć się o co chodzi. Proszę brak trywialnych odpowiedzi w stylu "sam se dojdź", bo sam sobie dochodzę od kilku godzin. Interesują mnie za to zupełnie inne rozwiązania problemu (*) niż ta moja karma z operatorami << i w tym miejscu uwaga - jeszcze wcześniej kilka godzin poświęciłem na rozważanie rozwiązania opierającego się o va_list, tego też już nie chcę :).
pozdro 600

0
class Element {
    private:
        int value;
    public:
        Element(int v) : value(v) { }
        void print(ostream& o) const { o << value; }
    };

class Kolekcja {
    public:
        std::vector<const Element*> all;

        Kolekcja() { }
        Kolekcja(const Element& first) {
            (*this) << first;
            }
        Kolekcja& operator<<(const Element& value) {
            all.push_back(&value);
            return *this;
            }
        Kolekcja& operator<<(const Kolekcja& value) {
            all.insert(all.begin(), value.all.begin(), value.all.end());
            return *this;
            }
        friend Kolekcja operator<<(const Element& first, const Element& second) {
            return Kolekcja(first, second);
            }

        void printall(ostream& o) {
            for(int i=0; i<all.size(); i++) all[i]->print(o);
            }

    protected:
        Kolekcja(const Element& first, const Element& second) {
            (*this) << first;
            (*this) << second;
            }
    };

i możesz zrobić nawet tak:

( Element(31) << Element(7) << Element(14) ).printall(cout);

czyli każde wyrażenie Element << Element staje się Kolekcją. Możesz je np przekazać funkcji, która kolekcji oczekuje. O to chodziło?

Dopisane:
pojedynczy element też możesz przekazać do funkcji, która oczekuje kolekcji.
niestety nie da się już tak:

( Element(31) ).printall(cout);

ale to oczywiste raczej ;) Przy funkcji kompilator dopasuje argument automatycznie, w takim inline trzeba by mu było podpowiedzieć.

0

Ło panie!
Dokładnie o to chodziło. A nawet o mniej niż to. Tu, widzę, operatory są pozamiatane doszczętnie :).
W międzyczasie sam postanowiłem zrobić jakiś testbed do tego zagadnienia i go tu wkleić i nawet osiągnąłem to co chciałem, ale ten Twój patent potrafi więcej...
Dla kompletności go wkleję

#include <list.h>
#include <stdio.h>

using namespace std;
class bluzg;

class bluzgi
    {
    public:
        list<bluzg*> a;
        void bluzgaj();
        bluzgi&  operator << (bluzg* &b)
            {
            a.push_back(b);
            return(*this);
            }
    };

class bluzg {
    public:
        virtual char* wal()=0;

        bluzgi operator << (bluzg* &b2)
            {
            bluzgi bl;
            bluzg *b1=this;
            bl << b1 << b2;
            return(bl);
            }

        bluzgi operator << (bluzg &b2)
            {
            return(operator << (b2));
            }
    };

void bluzgi::bluzgaj()
   {
   list<bluzg*>::iterator i = a.begin();
   while (i!=a.end()) {
       printf((*i)->wal());
       i++;
       }
   }

class karamba : public bluzg{
    public:
        virtual char* wal() {return("karamba, ");}
    };

class motyla : public bluzg{
    public:
        virtual char* wal() {return("motyla ");}
    };

class noga : public bluzg{
    public:
        virtual char* wal() {return("noga!!\n");}
    };


void Sprawdz(bluzgi &b)
    {
    b.bluzgaj();
    }

int main(int argc, char* argv[])
{
bluzgi b;
karamba k;
motyla m;
noga n;
b << &k << &m << &n;
b.bluzgaj(); //OK
printf("\n");

Sprawdz(b << &k << &m << &n); //OK
printf("\n");

Sprawdz(bluzgi() << &k << &m << &n); //OK
printf("\n");

Sprawdz( k << &m << &n); //OK
printf("\n");

Sprawdz( karamba() << &motyla() << &noga()); //OK
printf("\n");

system("PAUSE");
return 0;
}

niezbyt to piękne, kompiluje się z jakimiś ostrzeżeniami i nie można tak mieszać, ale działa.
pozdrawiam i dzięki za szybką odpowiedź.

0

a nie, jednak nie jest to dokładnie to co chciałem, tylko na pierwszy rzut oka tak wyglądało. Problem w tym, że Twoje operatory << działają na referencjach do elementów kolekcji. W moim przypadku po klasie Element dziedziczy kilka dalszych klas i w zasadzie to one są używane. Czyli, gdyby było

class E1 : public Element {...};
class E2 : public Element {...};
//to nie zadziałałoby
E1 e1;
E2 e2;
Kolekcja << e1 << e2;

W moim kodzie zrobiło się jeszcze lepiej po wywaleniu referencji z argumentów op. <<, sygnatury tych funkcji teraz są następujące:

        bluzgi&  bluzgi::operator << (bluzg* b)
        bluzgi bluzg::operator << (bluzg* b2)

Jedyna rzecz, która mnie jeszcze irytuje to konieczność przekazania pierwszego elementu kaskady przez wartość, a dopiero następnych przez wskaźnik,

Sprawdz( karamba() << &motyla() << &noga()); 

ale tego chyba się nie da uniknąć

0

Eeej, a dlaczego niby nie miałoby zadziałać :> ? Dajesz zwykłe wirtualne funkcje, żeby polimorfizm był i jedziesz:

class Element {
    public:
        virtual void print(ostream& o) const  = 0;
    };

class A: public Element {
    public:
        A(int v) { /* ... */ }
        virtual void print(ostream& o) const { /* ... */ }
    };

class B: public Element {
    public:
        B(int v) { /* ... */ }
        virtual void print(ostream& o) const { /* ... */ }
    };

class C: public Element {
    public:
        B(bool v) { /* ... */ }
        virtual void print(ostream& o) const { /* ... */ }
    };

// ....

(A(23) << B(5) << C(2+3!=5) ).printall(cout);

Jedyna niedogodność: trochę wyconstowałem to wszystko, żeby dało się obiekty tymczasowe wrzucać. Aby nie mieć constów i jednocześnie z palca tymczasowe przekazywać, trzeba trochę chamskiego rzutowania. Poniżej wersja, w której przykładowa funkcja print nie musi mieć postaci:

virtual void print(ostream& o) const 

może być taka:

virtual void print(ostream& o)

modyfikowanie tymczasowych jest bezpieczne, chociaż bezsensowne, więc bez obaw na widok const_casta ;)

class Element {
    public:
        virtual void print(ostream& o)  = 0;
    };

class FromFloat : public Element {
    private:
        float value;
    public:
        FromFloat(float v) : value(v) { }
        virtual void print(ostream& o) { o << value << " ";  }
    };

class ToHex : public Element {
    private:
        int value;
    public:
        ToHex(int v) : value(v) { }
        virtual void print(ostream& o) { o << hex << value << " "; }
    };

class Kolekcja {
    public:
        std::vector<Element*> all;

        Kolekcja() { }
        Kolekcja(const Element& first) {
            (*this) << first;
            }
        Kolekcja& operator<<(const Element& value) {
            all.push_back( const_cast<Element*>(&value) );
            return *this;
            }
        Kolekcja& operator<<(const Kolekcja& value) {
            all.insert(all.begin(), value.all.begin(), value.all.end());
            return *this;
            }
        friend Kolekcja operator<<(const Element& first, const Element& second) {
            return Kolekcja(first, second);
            }

        void printall(ostream& o) {
            for(int i=0; i<all.size(); i++) all[i]->print(o);
            }

    protected:
        Kolekcja(const Element& first, const Element& second) {
            (*this) << first;
            (*this) << second;
            }
    };

i użycie bez zmian:

(ToHex(192) << FromFloat(3.14) << ToHex(255) ).printall(out);
0

Rzeczywiście... O ile dobrze rozumiem to haczyk polega na tym, że kompilator rzutuje domyślnie nie tylko wskaźniki typów pochodnych na wskaźnik typu bazowego - robi tak też bez marudzenia z referencjami. Zawsze traktowałem przekazywanie argumentów przez referencję jako pewną sztuczkę - przekazujemy coś przez wskaźnik ale z poziomu funkcji działamy jak gdyby przekazanie było przez wartość. Myślałem że reguły rzutowania są takie same jak w przypadku przekazywania przez wartość, a to, okazuje się, referencje przejęły po wskaźnikach...
C++ i meandry domyślnego rzutowania.... :)
Dzięki jeszcze raz za pomoc...

0

przekazanie argumentu przez referencje, nie rozni sie absolutnie niczym od przekazania przez const-wskaznik. to jest jedynie inny zapis, zeby nie trzeba bylo stawiac miliona * w dziwnych miejcach

1 użytkowników online, w tym zalogowanych: 0, gości: 1