Tablica z domknięciami zwracającymi leniwie instancje klas

1

Panowie czy mozliwe jest w c++ utworzenie mapy zawierajacej closures ktore zwracaja (leniwie) instancje klas? Jesli tak to moze ktos rzucic "kawalkiem kodu" dla wyjasnienia?

1

Coś takiego przychodzi na myśl:

class Base {…};

class A: public Base {…};
class B: public Base {…};

using type_map_t = std::map<std::string, std::function<Base*()>>;
template<class T> std::function<Base*()> create() {
  return []() -> Base* {return new T;}
};

int main() {
  type_map_t type_map = {
             {"A"s, create<A>()},
             {"B"s, create<B>()},
  };
  Base* base = type_map["A"s]();
  …;
  return 0;
}
0

@Althorion - dziekuje Ci bardzo. Mam jeszcze jedno pytanie (sorry, jestem php-owcem, a z C++ mam do czynienia od wczoraj).
Z tego co widze poslugujesz sie jednynm interfejsem - co w przypadku kontenera DI budowanego w oparciu o taka mape jest sporym ograniczeniem.
Czy mozliwe jest rozszerzenie przykladowej templatki ktora sie posluzyles na bardziej ogolne przypadki tzn na cos w stylu:

...
template<class T> std::function<TT*()> create() {
  return []() -> TT* {return new T;}
};
...

Mapa rowniez ma ograniczenie do Base:

...
using type_map_t = std::map<std::string, std::function<Base*()>>;
...

a fajnie byloby miec mozliwosc:

...
using type_map_t = std::map<std::string, std::function<TT*()>>;
...

W przeciwnym przypadku do kazdego interfejsu nalezaloby utzorzyc mape i templatke, a tworzenie instancji niezbedych obiektow w kodzie realizowac jako przeszukiwanie wszystkich map w kontenerze w poszukiwaniu klucza odpowadajacego parametrowi wywolania metody get.

Do takiego przeszukiwania map, pewnie rowniez nalezaloby przygotowac jakis pojemnik (tablice?) ktora trzeba by "przeleciec" foreach'em?
Jak myslisz?

0

Templatkę sobie możesz bardziej sparametryzować, jeśli chcesz; ale z mapą już gorzej — C++ musi na etapie kompilacji wiedzieć, co tam siedzi, żeby móc potem tego używać. Tzn. możesz sobie zrobić i void *, ale potem nie wywołasz żadnej metody takiego obiektu ani nic.

Co do rozwiązań architektonicznych — nie bardzo wiem, co robisz, ani po co. Brzmi to jak jakieś akrobacje, ale jestem zmęczony długim tygodniem i może czegoś nie dostrzegam…

0

@Althorion jeszcze raz dzieki za reakcje.

Pokrotce chodzi o to ze w php moge sobie wprost zadeklarowac taka przykladowo tablice closures:

    $map = [
        ...
        ModuleConfigInterface::class => static function (): ModuleConfig {
            return new ModuleConfig();
        },
        ...
    ]

"zaladowac" ja do kontenera a potem juz dobierac sobie wymagane instancje obiektow w kodzie:

        $map = [
        ...
        ModuleConfigInterface::class => static function (): ModuleConfig {
            return new ModuleConfig();
        },

        ModuleInterface::class => static function (ContainerInterface $container): Module {
            return new Module(
                $container->get(ModuleInterface::class),
            );
        },
        ...
    ];

w nastepujacy sposob:

$module = $container->get(ModuleInterface::class);

Zastanawialem sie czy cos takiego jest mozliwe w C++. Pytam bo przymierzam sie do napisania php'oweg rozszerzenia (mozna to robic w C lub C++) zeby napisac silnik do NN. W samym php nie ma to sensu bo strasznie wolno cos takiego chodzi i zastanawiam sie jakie osiagi mialby kod kod odpalany w php ale wykorzystujacy rozszerzenie napisane w C++.

5

Do czego konkretnie ma to służyć? Bo dla mnie to wygląda na problem XY.

Może uproszczę do głupoty to co podał @Althorion:

#include <iostream>
#include <functional>
#include <string>
#include <map>
using namespace std;

class Car { public: virtual void what() { cout<<"Car"<<endl; } };
class Toyota: public Car { public: virtual void what() { cout<<"Toyota"<<endl; } };
class Audi: public Car { public: virtual void what() { cout<<"Audi"<<endl; } };
class CarFactory
{
	private:
	static const map<string,function<Car*()>> &factory()
	{
		static const map<string,function<Car*()>> map
		{
			{"Toyota", []() -> Car *{ return new Toyota; } },
			{"Audi", []() -> Car *{ return new Audi; } },
		};
		return map;
	}
	public:
	static Car *make(const string kind) { return factory().at(kind)(); }
};

class Person { public: virtual void who() { cout<<"Person"<<endl; } };
class Student: public Person { public: virtual void who() { cout<<"Student"<<endl; } };
class Teacher: public Person { public: virtual void who() { cout<<"Teacher"<<endl; } };
class PersonFactory
{
	private:
	static const map<string,function<Person*()>> &factory()
	{
		static const map<string,function<Person*()>> map
		{
			{"Student", []() -> Person *{ return new Student; } },
			{"Teacher", []() -> Person *{ return new Teacher; } },
		};
		return map;
	}
	public:
	static Person *make(const string kind) { return factory().at(kind)(); }
};

int main()
{
	CarFactory::make("Toyota")->what();
	CarFactory::make("Audi")->what();
	
	PersonFactory::make("Student")->who();
	PersonFactory::make("Teacher")->who();
	
	return 0;
}

Z tego co piszesz to chcesz mieć możliwość albo:

	CreasyFactory["Student"]()-> ... who() czy what() oto jest pytanie
	CreasyFactory["Teacher"]()-> ... who() czy what() oto jest pytanie

albo uprościć deklaracje obu fabryk ale tego też niezbyt można uprościć oprócz przedeklaracji map<string,function<Person*()>> które w użyciu by wyglądało jakoś tak:

	static const FactoryMap<Person> &factory()
	{
		static const FactoryMap<Person> map
		{

2 wiersze z 13+(ilość wariantów) nieco prostsze ale mega zysk!

0

@_13th_Dragon dzieki za reakcje.

Do czego konkretnie ma to służyć?

Gdyby nadgorliwy moderator nie zredagowal tytulu, byloby pewnie jasniej, ale do rzeczy. Chodzi o zbudowanie w C++ DI kontenera dzialajacego z grubsza podobnie do tego ktorego uzywamy w php (w Java'ie da sie to zrobic - probowalem).

Zatem mamy w php pewien zalecany standard w postaci interfejsu dla kontenerow PSR-11:

<?php

namespace Psr\Container;

interface ContainerInterface
{
    /**
     * Finds an entry of the container by its identifier and returns it.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
     * @throws ContainerExceptionInterface Error while retrieving the entry.
     *
     * @return mixed Entry.
     */
    public function get($id);

    /**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @return bool
     */
    public function has($id);
}


DI kontener implementujacy taki interfejs, zawiera rowniez mapper'a callback'ow (w najprostszym ujeciu tablice) tworzacych instancje wymaganych obiektow.

Przykladowa implementacje takiego interfejsu umieszczono tu na forum w watku Własnoręczny Dependency Injection Container

O ile to mozliwe chcialbym zrobic cos podobnego w C++. Dziekuje Ci za Twoj kod, ale zanim sie do niego odniose musze sie mu blizej przyjrzec - tak jak wspomnialem wczesniej, C++ zobaczylem pierwszy raz na oczy wczoraj.

0

Zobacz IServiceProvider z C# i zrób analogicznie.

0

@proximus-prime: Patrzyłeś na jakieś gotowce? np. https://github.com/google/fruit

2

Nie rozumiem dlaczego nie możesz po prostu użyć wzorca factory. Wydaje mi się to dziwne.

0

@yarel - dzieki za linka, juz wczesniej zapoznalem sie w tym rozwiazaniem, ale to caly czas nie to o co michodzi.

Ostatecznie chyba jednak pierwsze podejscie @Althorion'a Coś takiego przychodzi na myśl: wydaje sie byc najblizsze moim oczekiwaniom.

Dzieki Panowie za pomoc.

1

@proximus-prime: Myślę, że jakbyś rozpisał w pseudokodzie krok po kroku dwa przykładowe przypadki to byłoby prościej.

3

Może type safe unions aka variants będą wystarczającą protezą?

#include <string>
#include <iostream>
#include <variant>
#include <map>

class A { int x;  public: A():x(0){}; A(int x):x(x){}; int value() { return x; } };
class B { std::string x; public:  B(std::string x):x(x) {};  std::string value() { return x; } };
class C { double x;  public: C(double x):x(x) {}; double value() { return x; } };

enum Klucz {Jablko, Gruszka, Pietruszka};

int main() {
    std::map<Klucz,std::variant<A,B,C>> container;

    container[Jablko] = A(3);
    container[Gruszka] = B("blah");
    container[Pietruszka] = C(3.1415);

    A a = std::get<A>(container[Jablko]);
    B b = std::get<B>(container[Gruszka]);
    C c = std::get<C>(container[Pietruszka]);

    std::cout << "A = " << a.value() << std::endl;
    std::cout << "B = " << b.value() << std::endl;
    std::cout << "C = " << c.value() << std::endl;
	
    try {
        a = std::get<A>(container[Gruszka]); 
    } catch (std::bad_variant_access &err) {
        std::cout << "Fackup!" << std::endl;
    }

    return 0;
}

Nie rozumiem tylko dlaczego google fruit nie spełnia Twoich oczekiwań co do DI ?

1

@yarel - Nie rozumiem tylko dlaczego google fruit nie spełnia Twoich oczekiwań co do DI ?

Najprawdopodobniej dlatego ze skladnia c++ w porownaniu do php jest dla mnie chwilowo "deczko" zbyt zawila i pewnie pewne rzeczy mi umykaja - ale na pewno sobie to jeszcze na spokojnie przeanalizuje.

@yarel - Może type safe unions aka variants będą wystarczającą protezą?

Dziekuje Ci za ten kod, na pewno go sobie przeanalizuje.

@Czitels: Myślę, że jakbyś rozpisał w pseudokodzie krok po kroku dwa przykładowe przypadki to byłoby prościej.

Chwilowo zawieszam ta dyskusje, do momentu nabrania pewnosci ze do konca rozumiem to co juz tutaj mi zaproponowano.

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