Forum: PC-Programmierung [C++] universelle Funktionen für abgeleitete Klassen


von B. S. (bestucki)


Lesenswert?

Hallo zusammen

Ich sitze gerade an einem Problem, für das hoffentlich eine 
einigermassen bequeme Lösung existiert.

Ich habe folgende Situation:
1
class base{
2
  public:
3
    typedef std::unique_ptr<base> ptr;
4
    virtual ~base(){}
5
    
6
    base(const base &) = delete;
7
    base(base &&) = delete;
8
    base & operator=(const base &) = delete;
9
    base & operator=(base &&) = delete;
10
  
11
  protected:
12
    base(){}
13
};
14
15
16
class derived_a :
17
  public base
18
{
19
  public:
20
    virtual ~derived_a(){}
21
    static base::ptr Create(){
22
      return base::ptr{new derived_a{}};
23
    }
24
  
25
  private:
26
    derived_a()
27
    : base{}
28
    {}
29
};
30
31
32
class derived_b :
33
  public base
34
{
35
  public:
36
    virtual ~derived_b(){}
37
    static base::ptr Create(int A, double B){
38
      return base::ptr{new derived_b{A, B}};
39
    }
40
  
41
  private:
42
    derived_b(int A, double B)
43
    : base{}, A_{A}, B_{B}
44
    {}
45
    
46
    int A_;
47
    double B_;
48
};

Nun möchte ich eine Funktion folgender Art,
1
std::vector<base::ptr> Generate(std::size_t N /* evt. zusaetzliche Argumente */ );
die einen vector mit N Elementen erstellt. Ob der vector nun mit 
derived_a oder derived_b Objekten gefüllt werden soll, steht vor dem 
Compilieren fest. Nur möchte ich nicht für jede abgeleitete Klasse eine 
eigene Funktion schreiben (momentan wären es nur 4, später jedoch mehr), 
die im Grunde alle genau das Gleiche tun.

Den Weg über Templates soll mir recht sein, nur weiss ich nicht, wie ich 
die verschiedene Anzahl Argumente der Create Funktionen handhaben soll. 
Irgendwie muss ich diese ja auch der Funktion Generate übergeben. 
Zusätzlich sind nicht immer alle Argumente vom gleichen Typ. Falls nötig 
ist es möglich, move Konstruktoren und move operator= Funktionen zu 
definieren. Die habe ich weggelassen, da bisher nicht benötigt.

Kennt jemand eine praktikable Lösung dafür, oder ist das, was ich da 
machen will, absoluter Schwachsinn?

von Dr. Sommer (Gast)


Lesenswert?

Du brauchst variadische Templates:
1
#include <cstddef>
2
#include <vector>
3
4
template <typename T, typename... Args>
5
std::vector<base::ptr> Generate (size_t N, Args&... args) {
6
  std::vector<base::ptr> vec;
7
  vec.reserve (N); // wegen Effizienz
8
  for (size_t i = 0; i < N; ++i)
9
    vec.emplace_back (std::make_unique<T> (args...));  // Auch wegen Effizienz
10
  return vec;
11
}
Wenn du nur effizient kopierbare Datentypen wie "int" übergibst, macht 
es Sinn das "Args&" duch "Args" zu ersetzen. Universelle Referenzen kann 
man hier leider nicht wirklich einsetzen. Die Werte werden 
logischerweise in jedem Fall kopiert, wenn du zB einen std::vector als 
Argument übergibst, wird es N Kopien geben, eine rvalue/move 
construction Optimierung geht hier nicht.

von Dr. Sommer (Gast)


Lesenswert?

PS: Ich bin blind. Mit deiner Create Funktion sieht es so aus:
1
#include <cstddef>
2
#include <vector>
3
4
template <typename T, typename... Args>
5
std::vector<base::ptr> Generate (size_t N, Args&... args) {
6
  std::vector<base::ptr> vec;
7
  vec.reserve (N); // wegen Effizienz
8
  for (size_t i = 0; i < N; ++i)
9
    vec.emplace_back (T::Create (args...));  // Auch wegen Effizienz
10
  return vec;
11
}

von Variadic Templates (Gast)


Lesenswert?

be s. schrieb:
> Den Weg über Templates soll mir recht sein, nur weiss ich nicht, wie ich
> die verschiedene Anzahl Argumente der Create Funktionen handhaben soll.

Da du ja eh schon C++11 einsetzt, lässt sich das relativ leicht mittels 
Variadic Templates lösen:

1
template <typename TYPE, typename... ARGS>
2
std::vector<base::ptr> Generate(std::size_t N, ARGS... args)
3
{
4
    std::vector<base::ptr> vec;
5
6
    while (N-- > 0)
7
        vec.push_back(TYPE::Create(args...));
8
9
    return v;
10
}

Alternativ kann man auch mit Initializer Lists arbeiten, dann musst du 
aber noch deine Create() entsprechend überladen.

be s. schrieb:
> oder ist das, was ich da machen will, absoluter Schwachsinn?

Ob dieser Ansatz insgesamt sinnvoll ist oder ob es vielleicht nicht doch 
eine elegantere Lösung gibt, hängt halt vom konkreten Anwendungsfall ab. 
Kann man aber durchaus so machen.

von B. S. (bestucki)


Lesenswert?

Vielen Dank für die Antworten.

Dr. Sommer schrieb:
> Du brauchst variadische Templates:

Die hab ich noch nie verwendet, da muss ich mich erst ein wenig 
einlesen.

Dr. Sommer schrieb:
> Wenn du nur effizient kopierbare Datentypen wie "int" übergibst, macht
> es Sinn das "Args&" duch "Args" zu ersetzen.

Bisher werden nur Integer und doubles übergeben, für grössere Typen sehe 
ich momentan keinen Bedarf.

Variadic Templates schrieb:
> Alternativ kann man auch mit Initializer Lists arbeiten, dann musst du
> aber noch deine Create() entsprechend überladen.

Habe ich mir auch schon überlegt, dies funktioniert aber nur, solange 
alle Argumente vom gleichen Typ sind.

Variadic Templates schrieb:
> Ob dieser Ansatz insgesamt sinnvoll ist oder ob es vielleicht nicht doch
> eine elegantere Lösung gibt, hängt halt vom konkreten Anwendungsfall ab.
> Kann man aber durchaus so machen.

Hintergrund: Gegeben ist ein zweidimensionales Feld, in das Objekte 
"gezeichnet" werden sollen (Linie, Kreis etc.), natürlich alles 
parametrisierbar (Grösse usw.). Zusätzlich müssen die Eigenschaften der 
Objekte definiert werden (Argumente für Create-Funktionen). Die Objekte 
sind zunächst alle identisch, entwickeln später jedoch je nach Standort 
und Nachbarn ein "Eigenleben" und sind damit einzigartig, deswegen will 
ich diese nicht kopieren können.
Meine Generate-Funktion wird so nirgends in meinen Code verwendet 
werden, ist aber das einfachste Minimalbeispiel, das mir in den Sinn 
gekommen ist, ohne viel drumherum erklären zu müssen. Wenns vom Prinzip 
her mit meiner Generate-Funktion funktioniert, schaffe ich das auch, das 
Prinzip auch meine Funktionen zu übertragen.

Ich werde mich mal einlesen und es mit den validarischen Templates 
versuchen, aber jetzt gibts erstmal Abendessen.

von Tom (Gast)


Lesenswert?

Eine andere Möglichkeit wäre es, der Generate etwas 
factory-method-ähnliches mitzugeben. Ich bin aber nicht davon überzeugt, 
dass man damit außer Codelänge und Komplexität irgendetwas gewinnt.
1
template<typename Generator>
2
std::vector<base::ptr> Generate(std::size_t N, Generator gen)
3
{
4
    std::vector<base::ptr> result;
5
    result.reserve(N);    
6
    for (size_t i = 0; i < N; ++i)
7
        result.emplace_back(gen());
8
    return result;
9
}
10
11
int main()
12
{
13
    auto a_builder = [](){return derived_a::Create();};
14
    auto a = Generate(3, a_builder);
15
16
    float x{0};
17
    float y{0};
18
    auto b_builder = [&x, &y](){x +=1; y +=10; return derived_b::Create(x,y);};
19
    auto b = Generate(4, b_builder);
20
21
    bool is_a = false;
22
    float p{0.1};
23
    float q{0.1};
24
    auto c_builder = [&is_a, &p, &q](){p +=1; q +=10; is_a = !is_a; return is_a ? derived_a::Create() : derived_b::Create(p,q);};
25
    auto c = Generate(5, c_builder);
26
}

von Dr. Sommer (Gast)


Lesenswert?

Tom schrieb:
> Eine andere Möglichkeit wäre es, der Generate etwas
> factory-method-ähnliches mitzugeben.
Und diese Generate Version macht praktisch das Gleiche wie 
std::generate... ;-)

von Tom (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Und diese Generate Version macht praktisch das Gleiche wie
> std::generate... ;-)

Dass die Generate() ziemlich überflüssig ist, ist mir auch aufgefallen, 
aber als Minimalbeispiel des OP für "Funktion will Objekte erzeugen, 
weiß aber selbst nicht, welche oder wie das im Einzelnen geht." ist sie 
sehr passend.

von B. S. (bestucki)


Lesenswert?

Vielen Dank, ihr habt mir sehr geholfen. Habs mit validarischen 
Templates gelöst und es funktioniert wie gewünscht.

von Daniel A. (daniel-a)


Lesenswert?

be s. schrieb:
> validarischen

validarischen = überprüfend, du meinst variadic (variadisch) = variable 
argumentnummer.

von B. S. (bestucki)


Lesenswert?

Daniel A. schrieb:
> validarischen = überprüfend, du meinst variadic (variadisch) = variable
> argumentnummer.

Danke für die Korrektur. Ich hab mir ehrlich gesagt das Wort nie richtig 
angeschaut...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.