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?
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;
}
@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?
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…
@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++.
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!
@_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.
Zobacz IServiceProvider
z C# i zrób analogicznie.
@proximus-prime: Patrzyłeś na jakieś gotowce? np. https://github.com/google/fruit
Nie rozumiem dlaczego nie możesz po prostu użyć wzorca factory. Wydaje mi się to dziwne.
@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.
@proximus-prime: Myślę, że jakbyś rozpisał w pseudokodzie krok po kroku dwa przykładowe przypadki to byłoby prościej.
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 ?
@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.