[gelöst] Variablen durch Makro oder Preprozessor erzeugen
-
Sone schrieb:
template <typename int i>Wait what?
Ist das eine VC++-Exotik oder was?Nö, eigentlich nicht. Ich glaube das läuft unter dem Topic "Template Specialization" ... o.G.
-
RockNix schrieb:
Weil solche Konstrukte nur mit "const int" und nicht mit "int" funktionieren.
Natürlich kannst du
int-Literale wie0als Templateargumente übergeben. Aber ich nehme an, du willst direkt 0 bis N erstellen.Musst du nachträglich noch Random Access auf die einzelnen
Bhaben? Sonst könntest du rekursiv alle Instanzen abspeichern:template <int I> class B { public: void exec() { ... next.exec(); } private: B<I-1> next; }; template <> class B<0> { public: void exec() {} };Anwendung:
int main() { B<12> b; b.exec(); }Bei zu vielen Elementen kann der Compiler wegen zu tiefer Rekursion abbrechen.
RockNix schrieb:
Nö, eigentlich nicht. Ich glaube das läuft unter dem Topic "Template Specialization" ... o.G.
Er meint, dass die Syntax falsch ist. Das
typenamesteht nur bei Typ-Parametern.
-
Ja, Random Access ist zwingend. Die exec() ist nur exemplarisch und vielleicht etwas unglücklich gewählt vom Namen her.
Das generische Interface hat die üblichen Treiberfunktionen wie create, open, start etc. gekapselt. Und ich habe eben Treiber von verschiedenen Herstellern und auch von Verschiedenen Typen.
Und es gibt eben einen Speziellen, der in seinem Callback eben keinen Context zurückliefert. Somit weiss ich nicht welcher Treiber zurückruft, wenn es meherer Instanzen vom selben Typ gibt.
Im grunde das alte Problem mit den statischen Callbacks in Klassen, nur das man meist von der API irgendwelche Context Pointer oder User Parameter hat, die in diesem speziellen Fall eben fehlen.
Ich bin mit dem Beispiel der Lösung eigentlich schon sehr nah, nur die Sache mit "N" ist noch offen.
-
Ich habe das Beispiel noch mal etwas geändert, um den Sinn besser heraus zu stellen:
#include <iostream> const int a0 = 0; const int a1 = 1; const int a2 = 2; const int a3 = 3; class IA { public: virtual void start() = 0; }; template <int i> class B : public IA { private: static B* m_This; public: B() { m_This = this; } virtual void start() { std::cout << "start at instance: " << m_This << std::endl; } static void callback() { std::cout << "callback " << &callback << " at instance: " << m_This << std::endl; return; } }; B<a0>* B<a0>::m_This; B<a1>* B<a1>::m_This; B<a2>* B<a2>::m_This; B<a3>* B<a3>::m_This; int main() { IA* k[4]; k[0] = new B<a0>(); k[1] = new B<a1>(); k[2] = new B<a2>(); k[3] = new B<a3>(); for( int x=0; x<4; x++) k[x]->start(); // diese Aufrufe würden vom Treiber des Herstellers kommen B<a0>::callback(); B<a1>::callback(); B<a2>::callback(); B<a3>::callback(); return 0; }Die Ausgabe ist wie folgt - man erkennt sehr schön, wie die statischen Callback quasi dupliziert wurden.
start at instance: 00337960 start at instance: 00337990 start at instance: 003379C0 start at instance: 003379F0 callback 00821087 at instance: 00337960 callback 00821069 at instance: 00337990 callback 008211BD at instance: 003379C0 callback 008211FE at instance: 003379F0 Drücken Sie eine beliebige Taste . . .
-
Und was hält dich nun davon ab, es so zu machen?
#include <iostream> class IA { public: virtual void start() = 0; }; template <int i> class B : public IA { private: static B* m_This; public: B() { m_This = this; } virtual void start() { std::cout << "start at instance: " << m_This << std::endl; } static void callback() { std::cout << "callback " << &callback << " at instance: " << m_This << std::endl; return; } }; B<0>* B<0>::m_This; B<1>* B<1>::m_This; B<2>* B<2>::m_This; B<3>* B<3>::m_This; int main() { IA* k[4]; k[0] = new B<0>(); k[1] = new B<1>(); k[2] = new B<2>(); k[3] = new B<3>(); for( int x=0; x<4; x++) k[x]->start(); // diese Aufrufe würden vom Treiber des Herstellers kommen B<0>::callback(); B<1>::callback(); B<2>::callback(); B<3>::callback(); return 0; }Dein Code compiliert übrigens nur mit dem Microsoft-Compiler, mit G++ beispielswise nicht.
-
Was mich davon abhält ? Gar nichts, ich suche nur noch die Lösung über N

Rein aus Interesse, was meckert denn der G++ an ?
-
Was g++ sagt, kannst du dir zb auf ideone.com angucken: http://ideone.com/dr9Wg5
codepad.org mag deinen code auch nicht: http://codepad.org/elrfRQ4O
Welcher Compiler dahinter steckt, weiß ich aber nicht. Es ist aber generell gut, sowas immer mal zu testen, damit der code auch standardkonform und portabel bleibt/wird.Ich meinte auch eher, warum du unbedingt diese Dinger mit const int a0, a1 usw. haben willst statt einfach direkt Literale zu verwenden.
Dabei, das automatisch bis n zu haben, kann ich dir leider nicht helfen. Nexus' Lösung reicht ja auch nicht. Mal schaun, was noch so kommt hier.

Komisch, dass das nötig ist, finde ich es trotzdem.
-
Guter Tip, vielen Dank. Auch wenn g++ für mich kein Thema ist hat es mich schon interessiert und hier ist nun eine funktionierende Version. Schon erstaunlich was VC++ so alles durchgehen lässt. Macht man sich wenig Gedanken, wenn man mit anderen Compilern so gut wie nie in Berührung kommt.
#include <iostream> const int a0 = 0; const int a1 = 1; const int a2 = 2; const int a3 = 3; class IA { public: virtual void start() = 0; }; template <int i> class B : public IA { private: static B* m_This; public: B(); virtual void start(); static void callback(); }; template<int i> B<i>::B() { m_This = this; } template <int i> void B<i>::start() { std::cout << "start at instance: " << m_This << std::endl; } template <int i> void B<i>::callback() { std::cout << "callback " << &callback << " at instance: " << m_This << std::endl; return; } template<int i> B<i>* B<i>::m_This; int main() { IA* k[4]; k[0] = new B<a0>(); k[1] = new B<a1>(); k[2] = new B<a2>(); k[3] = new B<a3>(); for( int x=0; x<4; x++) k[x]->start(); // diese Aufrufe würden vom Treiber des Herstellers kommen B<a0>::callback(); B<a1>::callback(); B<a2>::callback(); B<a3>::callback(); return 0; }Danke an alle erstmal an dieser Stelle für Eure Mühe !!!
-
Du machst so einiges komisch. Die Definition der statischen Variablen solltest du ein Mal schreiben, und nicht für jede Instanziierung. Die
a-Variablen brauchst du nach wie vor nicht. Und natürlich musst du Destruktoren virtuell machen und Speicher wieder freigeben, aber ich hoffe, das tust du in deinem richtigen Projekt schon.Du kannst meinen Ansatz mit Rekursion auch auf ein Funktionstemplate anwenden. Unter Verwendung von C++11 (
std::arrayundstd::unique_ptr) kann ein sauberer Ansatz so aussehen:#include <array> #include <memory> #include <iostream> // int-to-type Idiom für Überladung template <unsigned int I> struct Int {}; struct Base { typedef std::unique_ptr<Base> Ptr; virtual void exec() = 0; virtual ~Base() {} }; template <unsigned int I> struct Derived : Base { virtual void exec() { std::cout << "Derived<" << I << ">\n"; } }; // Template-rekursive Funktion template <unsigned int N, unsigned int I> void fillElements(std::array<Base::Ptr, N>& array, Int<I>) { array[I].reset(new Derived<I>); fillElements(array, Int<I+1>()); } // Abbruchbedingung für Rekursion template <unsigned int N> void fillElements(std::array<Base::Ptr, N>&, Int<N>) { }Anwendung:
int main() { std::array<Base::Ptr, 10> elements; fillElements(elements, Int<0>()); // fertig! for (auto itr = elements.begin(); itr != elements.end(); ++itr) (*itr)->exec(); }Wie du siehst, muss die Grösse nur ein einziges Mal angegeben werden. Du hast kein Copy&Paste, keine Schleife, kein Boilerplate-Code.
-
Nexus schrieb:
Du machst so einiges komisch. Die Definition der statischen Variablen solltest du ein Mal schreiben, und nicht für jede Instanziierung. Die
a-Variablen brauchst du nach wie vor nicht. Und natürlich musst du Destruktoren virtuell machen und Speicher wieder freigeben, aber ich hoffe, das tust du in deinem richtigen Projekt schon.Gebe ich Dir Recht in allen Punkten, und natürlich beschränken sich diese Unzulänglichkeiten nur auf mein kleines "Bastelbeispiel" für meinen Beitrag.
Kann hier ja schlecht ein paar 1000 Zeilen Code posten

-
Wichtiger als diese Anmerkung in meinem Post ist allerdings der Code

-
Nexus schrieb:
Wichtiger als diese Anmerkung in meinem Post ist allerdings der Code

Ich weiss, und DEN versuche ich gerade zu verstehen. Das braucht noch ein wenig

-
Ich glaube ich habe es verstanden, zumindest weiss ich schon mal WAS der Code macht, aber an manchen Stellen bin ich mir noch nicht so sicher WARUM er es macht.
Aber zumindest habe ich meinen Beispiel Code einmal angepasst und ja, es funktioniert. 1000 Dank dafür !!!
Hiern noch für Interessierte das modifizierte Beispiel:
#include <array> #include <iostream> #include <memory> // ------------------------- generic interface class IA { public: virtual void start() = 0; }; // ------------------------- driver implementation template <int i> class B : public IA { private: static B* m_This; public: B(); virtual void start(); static void callback(); }; template<int i> B<i>::B() { m_This = this; } template <int i> void B<i>::start() { std::cout << "start at instance: " << m_This << std::endl; } template <int i> void B<i>::callback() { std::cout << "callback " << &callback << " at instance: " << m_This << std::endl; return; } template<int i> B<i>* B<i>::m_This; // ------------------------- creation template template <unsigned int I> class Int {}; template <unsigned int N, unsigned int I> void create( std::array< std::shared_ptr<IA>, N>& array, Int<I>) { array[I].reset( new B<I>); create( array, Int<I+1>()); } template <unsigned int N> void create(std::array< std::shared_ptr<IA>, N>&, Int<N>) { return; } // ------------------------- test purpose only template <unsigned int N, unsigned int I> void callback( std::array< std::shared_ptr<IA>, N>& array, Int<I>) { (static_cast<B<I>*>(array[I].get()))->callback(); callback( array, Int<I+1>()); } template <unsigned int N> void callback(std::array< std::shared_ptr<IA>, N>&, Int<N>) { return; } // ------------------------- test it all int main() { const unsigned int NUM = 25; std::array< std::shared_ptr<IA>, NUM> l_IA; create( l_IA, Int<0>()); for (auto it = l_IA.begin(); it != l_IA.end(); ++it) (*it)->start(); callback( l_IA, Int<0>()); return 0; }Mir ist noch nicht ganz klar, warum man den Int() Wrapper (?) braucht und warum man als Parameternamen "array" verwenden kann / darf. Ich habe es einmal mit einem willkürlichen Namen probiert, was auch funktioniert. Gibt es da einen Unterschied ?
-
Intbrauchst du, damit du Funktionstemplates überladen kannst. Sonst müsstest du spezialisieren, was nicht partiell geht und auch sonst ein paar Überraschungen auf Lager hat. Ich vermeide Spezialisierung von Funktionstemplates grundsätzlich. Siehe auch More C++ Idioms.arrayist ein Bezeichner wie jeder andere, Visual Studio macht nur Syntax Highlighting, weil es ein Schlüsselwort in einer Spracherweiterung (C++/CLI) ist. Ist etwas nervig, ich weiss.Warum nimmst du
shared_ptrund nichtunique_ptr? Damit hast du recht viel unnötigen Overhead...
-
Ähm, ja, Synthax Highlighting, wie peinlich, ich sollte mal Pause machen

Deine Erklärung ist sehr gut. Mir war gar nicht so richtig klar das jeder Aufruf von fillElements() ein überladener Aufruf ist.
Die Sache mit den shared_ptr ist historisch begründet. Die ganze Bibliothek an der ich schreibe benutzt für das generische Interface shared_ptr. Daran lässt sich auch wohl nichts mehr ändern.