Kompozyt - polimorficzne wywołanie funkcji

0

cześć :)
mam coś na modłę kompozytu (wzorzec), który wygląda mniej więcej tak:

class CompositeView : public sf::Drawable, public ComponentView {
private:
    std::vector<CompositeView> composites;
public:
    CompositeView();
    virtual ~CompositeView();
    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
    void addComposite(CompositeView compositeView);
    std::string getName();
};

i implementacja:

//...
void CompositeView::draw(sf::RenderTarget &target, sf::RenderStates states) const {
    for (CompositeView compositeView : this->composites) {
        compositeView.draw(target, states);
    }
}

void CompositeView::addComposite(CompositeView compositeView) {
    this->composites.push_back(compositeView);
}
//...

Buduję w ten sposób używając addComposite zagnieżdżoną 'strukturę' następnie chciałbym na takiej strukturze wywołać draw więc kod powinien powoli schodzić w głąb tej struktury i wykonywać draw, jak np. tutaj:

class InitView : public CompositeView {
public:
    InitView();
    virtual ~InitView();
    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
};

i...

void drawText(sf::RenderTarget &target, sf::RenderStates states) {
    sf::Font font;
    if (!font.loadFromFile("C:\\Users\\janec\\Downloads\\Consolas.ttf")) {
        std::cout << "Can't read font!" << std::endl;
        exit(-1);
    }
    sf::Text text;
    text.setFont(font);
    text.setCharacterSize(48);
    text.setFillColor(sf::Color::Black);
    text.setString("GeoFly");

    target.draw(text, states);
}

void InitView::draw(sf::RenderTarget &target, sf::RenderStates states) const {
    drawText(target, states);
}

i gdzieś tam w kodzie dodanie kompozytu:

this->compositeView.addComposite(initView);

Problem w tym, o tyle o ile w pętli mam poprawny w tym przypadku InitView jako CompositeView to metoda draw z InitView nie jest wołana - why?

2

O ile dobrze zrozumiałem...
To std::vector<CompositeView> composites; to nie będzie vector polimorficznych obiektów, tak samo jak funkcja void addComposite(CompositeView compositeView); nie przyjmuje obiektów polimorficznych. Przy przekazywaniu przez wartość następuje slicing o obiekty są okrawane do postaci typu z powyższych deklaracji.

0

@alagner: polimorficzny nazwałem dlatego że wszystkie inne będą dziedziczyć po CompositeView (jak np. InitView) ale w vektorze są trzymane właśnie jako CompositeView
w takim razie nie powinienem używać referencji tylko pointery aby nie występował tzw. slicing?

5

Nie używasz referencji, tylko wartości. W tym przypadku skorzystałbym z wektora unique_ptr albo boostowego ptr_vectora.

3

Możesz używać referencji także ale tego nie robisz. W sygnaturach powyżej używasz wartości.
@kq podał Ci klasyczny sposób na wykonanie tego. Możesz też wykorzystać „polimorfizm dla wartości” https://m.youtube.com/watch?v=QGcVXgEVMJg. Mogą też to być templatki i variant (z std lub boosta)+visit (w boost chyba apply_visitor, to jest to samo).

0

ok, myślałem że używam referencji... widać brak wiedzy bo ja zawodowo w javie programuję ;)
Myślałem że w C++ mamy pointery i właśnie referencje, których wg mnie używam jak powyżej.
OK dzięki z odp. doczytam i pociągnę wątek w razie wątpliwości :)

5

W pewnym uproszczeniu to co Java nazywa referencją to tak naprawdę wskaźnik, a zmiana nazwy to zagrywka marketingowa aby powiedzieć, że "java nie ma wskaźników" (ale skądś ma NullPointerException ;​) )

// prawdziwe dla wszystkich innych typów (*)
using T = int;

// wartość T
T value; 

// wskaźnik na T, może być pusty (null), można rebindować,
// tj przypisać adres innej zmiennej
T* ptr;

// referencja do T - value. Zawsze istnieje**,
// jest to de facto alias tej zmiennej,
// nie da się go przenieść na inną zmienną,
// nie może być null (koniecznie musi być zainicjalizowany)
T& ref = value;

* - poza void, na void są tylko wskaźniki
** - są przypadki tzw. dangling references
0

Chciałbym odświeżyć temat.
Na następującym prostym przykładzie oczekuję, że metoda c zostanie wywołana z I - ale nie jest. Czy może chodzi o to, że cs nie jest memberem I?

main**.cpp**:

#include "composite/A.h"
#include "composite/B.h"
#include "composite/I.h"

int main() {
    B* b = new B();
    b->addB(new I());

    b->handle();// -> nic :(
    b->draw();// -> OK :)
    b->handle();// -> nic :(
    b->handle();// -> nic :(

    return 0;
}

A**.h**:

#include <vector>
#include "C.h"

class A {// component view
private:
    std::vector<C*> cs;
public:
    A();
    virtual ~A();
    virtual void addC(C* c);
    virtual void handle();
};

A**.cpp**:

#include "A.h"

A::A() {
}

A::~A() {

}

void A::addC(C *c) {
    this->cs.push_back(c);
}

void A::handle() {
    for (std::vector<C*>::iterator it = this->cs.begin(); it != cs.end(); ++it) {
        (*it)->c();
    }
}

B**.h**:

#include "A.h"

class B : public A {// composite view
private:
    std::vector<B*> bs;
public:
    B();
    virtual ~B();
    virtual void addB(B* b);
    virtual void draw();
};

B**.cpp**:

#include <iostream>
#include "B.h"

B::B() {
}

B::~B() {
}

void B::draw() {
    for (std::vector<B*>::iterator it = this->bs.begin(); it != this->bs.end(); ++it) {
        (*it)->draw();
    }
    std::cout << "B draw" << std::endl;
}

void B::addB(B *b) {
    this->bs.push_back(b);
}

I**.h**:

class I : public B {
public:
    I();
    virtual ~I();
    virtual void draw() override;
};

I**.cpp**:

#include <iostream>
#include "I.h"

I::I() {
    class : public virtual C {
        virtual void c() override {
            std::cout << "should work" << std::endl;
        }
    } c;
    this->addC(&c);
}

I::~I() {
}

void I::draw() {
    std::cout << "I draw" << std::endl;
}

C**.h**: (jest tyko .h - nie istnieje .cpp)

class C {
public:
    virtual ~C() {};
    virtual void c() = 0;
};
1

To nie Java. W konstruktorze I definiujesz zmienną c o automatycznym czasie życia. Zostaje ona zniszczona po wyjściu z funkcji, a Ty nadal używasz jej adresu, to błąd nr 1.

Jesteś w stanie opisać/narysować (UMLem np.) co chcesz uzyskać? Widziałeś ten przykład: https://www.bogotobogo.com/DesignPatterns/composite.php

0

W nomenklaturze kompozytu u mnie:
Component to B
Composite to I
Leaf (nie mam aktualnie)
Operation to draw

A jest nad B aby 'pogrupować' odpowiedzialności - tutaj mam wywołanie c a w B jest wywołanie draw

To co chcę uzyskać to dodawanie w I anonimowej implementacji bo może być wiele zmiennych jak c - np. potrzebuję implementacji na event myszy to dodaję nową anonimową implementację C (tak jak w konstruktorze I mam), potem dodaję następną anonimową do np. obsługi klawiszy i następną i następną -> taki mam design :/ - zaleta tego to że odpowiedzialność jest dla każdej klasy pochodnej od B w niej samej, mam tutaj dostęp do wnętrzności jak prywatne pola, metody...

2

To się raczej nie uda tak jak chcesz to teraz zrobić.
Ja bym poszedł w kierunku lambd i std::function albo innego type erasure. Przy czym jak dojdzie do tego SFML to wcale nie musi to być takie proste.

EDIT: przypatrzyłem się Twojemu przypadkowi (rano myślę lepiej ;)) ponownie i można to zrobić „po javovemu” i to zadziała. Ale idiomatyczne i typowe dla C++ będzie rozwiązanie z lambdą
https://godbolt.org/z/bxsMqd8s8

0

@alagner: Twój przykład działa w obu przypadkach :)
Natomiast podejrzewam, że również i vector zagnieżdżonej struktury 'kompozytów' też muszę przerobić w ten sposób bo już jak pospinam to rozwiązanie z dodawaniem anonimowych implementacji (z lambdą tudzież to drugie) - nie działa mi to, ale jak myśle to u mnie problem z vector'ami pointerów...

@alagner: przeklejam Twoje rozwiązanie gdyby zniknęło z godbolt

#include <iostream>
#include <functional>
#include <vector>
#include <memory>

struct Foo
{
    virtual ~Foo()=default;
    virtual void bar() = 0;
};

void usingVec()
{
    using std::vector;
    using std::unique_ptr;
    using std::make_unique;

    using ImmediateFooImpl = struct : public Foo {
        void bar() override {
            std::cout << "immediate anon interface impl\n";
        }
    };

    vector<unique_ptr<Foo>> v;
    v.push_back(make_unique<ImmediateFooImpl>());
    v.at(0)->bar();
}

void usingFkn()
{
    using std::function;
    using std::vector;

    vector<function<void()>> v;
    v.push_back([](){std::cout << "immediate anon lambda\n";});
    v.at(0)();
}

int main(int, char*[])
{
    return 0;
}

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