Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

22.03.2016, 19:09

Factory mit variadic template arguments

An der Lösung für folgendes Design-Problem beiße ich mir jetzt schon länger die Zähne aus und fange langsam an, daran zu zweifeln, dass es eine (schöne) Lösung dafür gibt. Ich versuche es möglichst kurz und verständlich zu halten.

Eine meiner zu instanziierenden Klassen:

C-/C++-Quelltext

1
2
3
4
5
class Class
{
public:
  Class(Type type, Foo foo);
};


Interface, um Details über die Instanziierung zu kapseln:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AbstractCreator
{
  virtual ~AbstractCreator();
  virtual Class* create( Type type, Foo foo ) const = 0;
};

class ConcreteCreator
{
public:
  virtual Class* create( Type type, Foo foo ) const override
  {
    return new Class( type, foo );
  }
};


In der Praxis wird das genutzt, um in Ableitungen der create-Methode runtime checks zu machen, ob die Instanziierung erlaubt ist.

Factory, die von clients instanziiert wird:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Factory
{
public:
  Factory
  {
    // fill object creator map on construction
    add( "ObjectName", new Creator<Class>)
  }
  
  Class* create( const std::string& name, Type type, Foo foo )
  {
    // fowards to map entry
    return _map[name]->create( type, foo );
  }
  
private:
 std::map<std::string, Creator*> _map;
}


Client code:

C-/C++-Quelltext

1
2
3
4
5
int main()
{
  Factory f;
  factory.create(name, type, foo);
}


Das schöne ist nun, dass die Klassen und Instanziierungsmethoden völlig transparent für den Client sind und er nicht z.B. schon seine Factory mit Parametertypen templaten muss. Dummerweise bin ich durch die rein abstrakte Methode aber immer fest an die Signatur mit zwei Parametern gebunden. Was aber z.B. wenn ich folgende Klasse einführen will:

C-/C++-Quelltext

1
2
3
4
5
class ExtClass
{
public:
  ExtClass(Type type, Foo foo, Param newParam);
};


Dann müsste ich schon damit anfangen, create zu überladen, und längerfristig schreit das nach variadic template arguments. Das Problem ist nur, ich kann eine Methode

C-/C++-Quelltext

1
2
template<typename... Args>
Class* create(Args... args);


nicht virtuell überladen. Was kann ich hier also tun? Vielleicht gibts es ja hier einen Template-Guru, der eine Idee dazu hat.
Die Lösungen, die ich bisher dazu gefunden habe, erfordern beide, dass man den AbstractCreator templaten muss, was ich gerne vermeiden würde. Alternativ bin ich auch für komplett neue Design-Vorschläge offen, mit denen sich das einfacher umsetzen ließe. ;)

2

22.03.2016, 19:34

Wenn ich dich richtig verstehe, suchst du folgendes:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
template <class Class, typename... Args>
Class& createUnit(Args&&... _args)
{
    auto ptr = std::make_unique<Class>(std::forward<Args>(_args)...);
    auto& ref = *ptr;
    // speichere den ptr irgendwo
    return ref;
}


Habe hier einen unique ptr verwendet, ist aber natürlich nicht nötig ;)
Genauso kannst du auch einen einfachen pointer zurück geben, ich arbeite da aber lieber mit Referenzen.

mfg

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »anti-freak« (22.03.2016, 20:06)


3

22.03.2016, 19:53

Die zurückgegebene Referenz wird doch dann ungültig? :hmm:

4

22.03.2016, 19:54

Wieso sollte sie ungültig werden? Ich gebe ja keine Referenz auf den unique ptr zurück sondern auf das Objekt innerhalb dieses ptrs und das hat immer die gleiche Adresse, bis es gelöscht wird (oder manuell gemoved).

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »anti-freak« (22.03.2016, 20:19)


5

22.03.2016, 20:03

Der unique_ptr und sein geowntes Objekt wird zerstört und die Referenz zeigt ins Leere, oder übersehe ich da grad was?

6

22.03.2016, 20:19

Achso, jetzt verstehe ich glaube ich auf was du hinaus willst :D Ja, momentan würde der ptr gelöscht und dadurch auch die Referenz ungültig. Deswegen habe ich da auch einen Kommentar mit "mach was mit dem ptr" eingefügt. Soll so viel heißen wie, speichere ihn in irgend einem vector oder sonstigen Objekt ;)

7

23.03.2016, 15:02

Wenn ich dich richtig verstehe, suchst du folgendes:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
template <class Class, typename... Args>
Class& createUnit(Args&&... _args)
{
    auto ptr = std::make_unique<Class>(std::forward<Args>(_args)...);
    auto& ref = *ptr;
    // speichere den ptr irgendwo
    return ref;
}


Ja genau, wobei createUnit bei mir dann in einer Klassenhierarchie steht und als virtuelle Methode überladen wird. Dass das nicht mehr geht, sobald variadic templates im Spiel sind, ist genau mein Problem. ;)

8

23.03.2016, 18:23

Warum ist denn die create Methode Bestandteil der Klasse die sie erstellen soll?
Spezialisiere deine create Methode doch einfach für jeden Use case der das erfordert.

9

23.03.2016, 21:16

Ich bin mir nicht sicher, ob klar ist, was ich meine. Deshalb noch mal zur Verdeutlichung:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class AbstractCreator
{
  virtual ~AbstractCreator();
  virtual Class* create( Type type, Foo foo ) const = 0;
};

class BasicCreator : public AbstractCreator
{
public:
  virtual Class* create( Type type, Foo foo ) const override
  {
    return new Class( type, foo );
  }
};

class CheckedCreator : public AbstractCreator
{
public:
  CheckedCreator(enum Identifier ident)
    : _ident(ident)
  { }

  virtual Class* create( Type type, Foo foo ) const override
  {
    return runtimeCheck(_ident) ? new Class( type, foo ) : nullptr;
  }

private:
  Identifier _ident;
};


Die map in der Factory kann ich dann so füllen und damit festlegen, ob und welche Bedingungen zum Erstellen einer Instanz erfüllt sein müssen:

C-/C++-Quelltext

1
2
3
4
5
6
Factory
  {
    // fill object creator map on construction
    add( "UncheckedInstance", new Creator<Class>);
    add( "CheckedInstance", new CheckedCreator<Class>(identifier));
  }


Derzeit könnte man die Hierachie wohl auflösen und beide Cases mit einer create-Methode abdecken, aber es ist ja durchaus möglich, dass in Zukunft noch mal eine andere Creation-Strategie dazukommt, und dann stehe ich wieder vor dem gleichen Problem.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

10

23.03.2016, 22:41

[...] Was kann ich hier also tun? Vielleicht gibts es ja hier einen Template-Guru, der eine Idee dazu hat.
Die Lösungen, die ich bisher dazu gefunden habe, erfordern beide, dass man den AbstractCreator templaten muss, was ich gerne vermeiden würde. Alternativ bin ich auch für komplett neue Design-Vorschläge offen, mit denen sich das einfacher umsetzen ließe. ;)

Ein Function-Template kann nicht virtual sein und daran gibt es leider nix zu rütteln. Sowas wäre eine rein konzeptionell schon sehr fragwürdige Konstruktion, was sollte deiner Meinung nach genau passieren, wenn ein solches virtual Function-Template einer Basisklasse auf einem polymorphen Objekt aufgerufen wird? Wie genau soll eine abgeleitete Klasse bestimmte Instanzen eines solchen Function-Template überschreiben? Müssen dann immer alle möglichen Instanzen des Template überschrieben werden oder kann nur ein Teil überschrieben werden? Wie findest du zur Laufzeit die passenden Funktionen (bedenke: die Menge aller möglichen Instanzen des Function-Template ist von unbeschränkter Mächtigkeit und du hast keinerlei Möglichkeit an einer Stelle im Programm zu wissen, welche Instanzen irgendjemand irgendwie irgendwo irgendwann vielleicht einmal noch überschreiben wird)?

Erklär vielleicht mal, wofür genau du eigentlich die abstract Factory zu brauchen meinst, dann können wir uns evtl. eine Lösung überlegen... ;)


Deswegen habe ich da auch einen Kommentar mit "mach was mit dem ptr" eingefügt. Soll so viel heißen wie, speichere ihn in irgend einem vector oder sonstigen Objekt ;)

Wieso sollte die Factory den Besitz am Objekt behalten, imo wäre es in der Regel sinnvoller, den Besitz am erzeugten Objekt an den Aufrufer der create() Methode weiterzugeben, also einfach den unique_ptr zu returnen... ;)

Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von »dot« (23.03.2016, 22:53)


Werbeanzeige