Gibt es sowas wie <?> unter C++?



  • Servus,

    einige Java-Programmierer kennen sicherlich das <?>-Konstrukt, um quasi zu sagen, dass einem die Typparameter wurscht sind. Gibt es sowas auch unter C++?

    Anwendungsbeispiel:
    Ich habe ein Template fuer eine Polygon-Klasse definiert, wo man einen int-Wert angeben kann. Anhand dieses Werts wird dann ein Array von Punkten festgelegt. Nun moechte ich aber von manchen Funktionen aus ein Polygon beliebiger Groesse zurueckgeben, allerdings darf ich dann nicht einfach Polygon* als Rueckgabewert angeben, sondern muss auch hier eine Groesse angeben.

    (Mir ist klar, dass ich das auch ohne Templates hinkriegen koennte, aber dann muesste ich das Array im Konstruktor initialisieren, was ich nicht moechte.)

    Geht das irgendwie? Oder kann ich das sofort vergessen?



  • Templates greifen zur Compileziet, sind also fuer statische Angelegenheiten zustaendig. Die Anzahl der Punkte in deinem Polygon ist dynamisch. Daraus folgt: Templates sind nicht geeignet fuer Polygone mit variabler Punktzahl.

    ist klar, dass ich das auch ohne Templates hinkriegen koennte, aber dann muesste ich das Array im Konstruktor initialisieren, was ich nicht moechte

    Au weiha, und vielleicht sowas wie std::vector oder std::list benutzen. Mal ehrlich, warum moechtest du das nicht?

    Geht das irgendwie?

    Es geht irgendwie. Aber void* ist nicht die Loesung.



  • Rock Lobster schrieb:

    (Mir ist klar, dass ich das auch ohne Templates hinkriegen koennte, aber dann muesste ich das Array im Konstruktor initialisieren, was ich nicht moechte.)

    Geht das irgendwie? Oder kann ich das sofort vergessen?

    Du hast dir die frage doch schon selber beantwortet - mit Templates ist das machbar. Warum du die Initilaisierung im Konstruktor nicht möchtest ist schleierhaft. Aus Geschmacksgründen auf eine unübliche/unbekannte Methode auszuweichen ist meist eine Fehlentscheidung.

    Im Übrigen ist C++ eben nicht Java, sondern hat seine eigenen Programmierparadigmen. Also sollte man auch nicht versuchen etwas "so wie in Java" zu realisieren.

    Wenn du uns das eigentliche Problem schilderst (ggf. mit Code) und nicht die Methode, mit der du das Problem lösen willst, dann kann dir eventuell besser geholfen werden.



  • Ich moechte kein new im Konstruktor haben, damit die Werte moeglichst auf dem Stack abgelegt werden. Im Moment habe ich die new-Version und das ist einfach zu langsam (da extrem viele dieser Polygone instanziiert werden).

    Der Grund warum ich nun auf die Template-Loesung will ist der folgende: Von Polygon habe ich eine Klasse namens Quad abgeleitet, und dieses ruft einfach den Polygon-Konstruktor mit dem int-Parameter 4 auf. Also wird die Groesse auf 4 gesetzt und ein Array in dieser Groesse erstellt. Nun wollte ich das wie gesagt aendern, um nicht mehr so viele news zu haben, und habe zwei Moeglichkeiten: Entweder ich leite Quad nicht mehr von Polygon ab, sondern vielleicht beide Klassen von einem gemeinsamen Interface, oder ich leite Quad von Polygon ab, aber verwende ein Template fuer Polygon, wo die Groesse direkt mitgegeben wird.

    Im Übrigen ist C++ eben nicht Java, sondern hat seine eigenen Programmierparadigmen. Also sollte man auch nicht versuchen etwas "so wie in Java" zu realisieren.

    In Java geht es nichtmal, Zahlen als Template-Argumente zu verwenden, also habe ich auch nicht vor, es "so wie in Java" zu machen. Ich weiss nur dass man in Java generalisieren kann, indem man statt eindeutiger Template-Argumente ein Fragezeichen angibt, und meine Frage war nur, ob es eine vergleichbare Moeglichkeit auch in C++ gibt.

    EDIT: Ich vergass zu erwaehnen, dass gerade die Quads wie bloed instanziiert werden. Genau DORT ist es mir zu langsam. Aber ich faend es halt nach wie vor huebsch, wenn Quad von Polygon abgeleitet waere.



  • Du kannst eine Interface IPolygon definieren, von dem Quad erbt und die entsprechenden Methoden implementiert. Dabei ist die Arraygroesse der Punkte fest a la Point3D points[4]. Andere Moeglichkeiten: eigene Speicherverwaltung, Nachnutzung von Objekten aus einem Pool, ...



  • Von Polygon habe ich eine Klasse namens Quad abgeleitet

    Welche Funktionalitaet hat denn "Quad" die "Polygon" nicht schon beinhaltet?

    da extrem viele dieser Polygone instanziiert werden

    Haeufig haben unterschiedliche Polygone gemeinsame Eckpunkte sodass auch ein gemeinsamer Pool von Eckpunkten Sinn macht und eine Allozierung pro Element entfaellt.



  • Quad ist in dem Fall eher als eine Art typedef anzusehen und ruft bisher einfach nur Polygon(4) auf. Zudem kann ich z.B. bei Vergleichsoperatoren einfach alle 4 Punkte vergleichen, ohne eine Schleife zu machen (wie es bei einem beliebig grossen Polygon der Fall waere).

    Das mit den gemeinsamen Eckpunkten kommt in meinem Anwendungsfall kaum vor, da handelt es sich eher um Polygone, die sich ueberlappen.

    EDIT: Uebrigens werden auch die anderen Polygone spaeter nie in ihrer Groesse veraendert, daher waere das mit Templates halt schon praktisch. Was mich gerade wundert: ich kann in der Quad-Klasse das points-Array einfach nochmal neu definieren, aber was laeuft dann eigentlich ab, wenn die Polygon-Methoden aufgerufen werden? Ist das dann einfach in Polygon::points und Quad::points unterteilt?



  • Generics != Templates

    Die Syntax mit den <>-Klammern ist zwar gleich, aber es passiert doch letztendlich was anderes. Generics werden zwar vom Compiler überprüft, aber der erzeugte Code ist wieder die java.lang.Object .



  • Rock Lobster schrieb:

    Servus,
    einige Java-Programmierer kennen sicherlich das <?>-Konstrukt, um quasi zu sagen, dass einem die Typparameter wurscht sind.

    Gibt es sowas auch unter C++?

    Nein. "Java Generics" ist etwas völlig anderes. Die Typparameter einer generischen Klasse gehören nicht wirklich zum Typ der Klasse ("Type Erasure"). Sie werden nur dazu benutzt, zur Compilezeit etwas mehr Typechecking durchzuführen und dem Nutzer das Typecasting abzunehmen. Das ganze funktioniert wirklich nur wegen der Indirektion (Objekte kann man nie "direkt halten" sondern nur über Referenzen erreichen) und über Laufzeitpolymorphie.

    In C++ könnte man das so machen:

    class abstraktes_poly {
      public:
        virtual ~abstraktes_poly() {}
        virtual abstraktes_poly* clone() const = 0;
        virtual int ecken() const = 0;
        virtual punkt ecke(int index) const = 0;
      };
    
      template<int Ecken>
      class konkretes_poly : public abstraktes_poly {
        punkt m_ecken[Ecken];
      public:
        ...Füge Code für Initialisierung ein...
        konkretes_poly<Ecken>* clone() const {return new poly<Ecken>(*this);}
        int ecken() const {return Ecken;}
        punkt ecke(int index) const {return m_ecken[index];}
      }
    
      int main() {
        auto_ptr<abstraktes_poly> apap1 ( new konkretes_poly<4> );
        auto_ptr<abstraktes_poly> apap2 ( new konkretes_poly<5> );
        cout << apap1->ecken();
        cout << apap2->ecken();
      }
    

    Statt auto_ptr kann man diese Zeiger natürlich auch in einem Objekt einer eigens dafür geschriebenen Klasse verwalten. Dadurch kann man auch Wert-Semantik emulieren. Das ganze nennt sich dann auch "C++ Type Erasure" und wird u.a. bei Boost.Function und Boost.Any angewendet.

    Gruß,
    SP



  • Hallo Rock Lobster,

    Rock Lobster schrieb:

    Ich moechte kein new im Konstruktor haben, damit die Werte moeglichst auf dem Stack abgelegt werden. Im Moment habe ich die new-Version und das ist einfach zu langsam (da extrem viele dieser Polygone instanziiert werden).

    Ich frage mich gerade wie du so viele Objekte auf dem Stack ablegst.
    (Rekursion schliesse ich einfach mal aus)
    Wo verwaltest du denn die ganzen Polygone? Kurz gesagt, ich befürchte dass
    du entweder jedesmal den Copy-Konstruktor aufrufst oder auf identische
    Speicherbereiche verweist.

    Gruß,
    CSpille



  • Also in einer wichtigen Initialisierungsroutine am Anfang des Programms wird erstmal ein Haufen Quads in einen std::vector gespeichert (und zwar direkt als Objekt, nicht als Pointer). Da waere es jetzt halt unguenstig, wenn pro Quad im Konstruktor ein new aufgerufen wird, da das ja den gesamten Vorgang sehr ausbremsen kann. Also ist es mir lieber, wenn die Arrays von vorneherein initialisiert werden.

    Ich werde nun aber ueberlegen ob ich Polygon und Quad nicht einfach getrennt als Klasse haben soll. Die Anzahl der Methoden und Operatoren haelt sich sehr stark in Grenzen, und es ist wahrscheinlich auch nicht notwendig, dass sie ineinander gecasted werden muessen, von daher ist es vielleicht wirklich die sinnvollste Loesung, einfach zwei unterschiedliche Klassen zu verwenden. Das urspruengliche Design ging halt von schoenem OOP aus, aber jetzt ist die Geschwindigkeit deutlich wichtiger als das Klassendesign.



  • am Anfang des Programms ... im Konstruktor ein new aufgerufen wird, da das ja den gesamten Vorgang sehr ausbremsen kann

    Omg. Das passiert am Anfang und nur ein Mal. D.h. es spielt fuer das restliche Programm keine Rolle? Und dieser Konjunktiv stoert mich. Hast du man getestet und nachgemessen, ob es ueberhaupt ein Performanceloch ist?

    ueberlegen ob ich Polygon und Quad nicht einfach getrennt

    Haeh, hast du dir die Posts nicht durchgelesen. Du kannst OOP und Geschwindigkeit haben. Warum stellst du erst die Frage und machst es dann doch ganz anders. Beratungsresistent ...



  • Rock Lobster schrieb:

    ...Nun moechte ich aber von manchen Funktionen aus ein Polygon beliebiger Groesse zurueckgeben, ...

    Warum nicht wieder ein Funktionstemplate? Dann muss allerdings der Aufrufer die Dimension schon wissen (und sei es nur implizit).

    template <int DIM>
    Polygon<DIM>* f(Polygon<DIM> const& dummy);
    
    template <int DIM>
    Polygon<DIM>* f(int DIM) { return f(Polygon<DIM>); }
    

    Gruß,

    Simon2.



  • Ich haette vielleicht nicht "Programm" sondern "Vorgang" sagen sollen. Dieser Vorgang dauert im ersten Schritt sehr lang, es kommen danach noch ein paar Schritte die weniger lange dauern. Und der Vorgang kommt in meinem jetzigen "Programm" nur einmal vor, aber spaeter soll er immer wieder vorkommen koennen.

    Nachgemessen habe ich auch. Es kommen Daten uebers Netzwerk, und die CPU ist auf Voll-Last, das Netzwerk aber nicht. Also ist wohl davon auszugehen, dass in meinem Code zu viel passiert, und aus diesem Grund waere es sinnvoll, dort ein paar Dinge zu aendern. Da nun in jedem einzelnen Quad-Konstruktor jedoch ein unnoetiges "new" gemacht wird, waere das zumindest mal ein erster Anhaltspunkt.

    Ich habe mich gerade mit einem Kollegen unterhalten und ihm auch die Version mit der gemeinsamen IPolygon-Basisklasse vorgeschlagen (hatte sogar schon angefangen zu implementieren), er meinte jedoch, ich soll auf OOP in dem Fall verzichten, weil durch virtuelle Funktionen auch wieder unnoetig Overhead entsteht, der bei solch einer simplen Extrem-Basis-Klasse wie es ein Quad nunmal ist einfach nicht sein muss. Das war der Grund, warum ich sagte, dass ich nun wohl doch lieber eine voellig eigene, OOP-freie Implementierung einer Quad-Klasse bevorzugen wuerde. Hat aber nix mit beratungsresistent zu tun.



  • @ Simon2: Vielen Dank, auf diese Idee bin ich bisher nicht gekommen. Jedoch denke ich, dass auch das in meinem Anwendungsfall nicht sinnvoll sein wird (da man vorher nicht weiss, wie viele Punkte das Polygon dann haben wird). Mir ist nun auch klar warum das mit Templates nicht funktionieren kann. Trotzdem danke fuer den Hinweis, vielleicht kann ich das mal an einer anderen Stelle gut gebrauchen.



  • CPU ... Volllast ... Netzwerk nicht ... erster Anhaltspunkt ...

    Mit Nachmessen meine ich eine Profiler zu benutzen und genau zu schauen, wo viel Rechenleistung verbraten wird und nicht aufs geradewohl irgendwas zu optimieren.

    Quad-Konstruktor jedoch ein unnoetiges "new"

    Und es wurde gezeigt, wie das zu beheben ist, mit OOP. Ausserdem waere dann ein Objektpool wahrscheinlich angebrachter.

    ich soll auf OOP in dem Fall verzichten, weil durch virtuelle Funktionen auch wieder unnoetig Overhead entsteht

    in jedem Fall ... Hast du ihn auch gefragt, aus was der Overhead besteht und wieviel Zeit er kosten wird? Es ist eine zusaetzlich Dereferenzierung eines Zeigers. Also verdammt billig.

    Ich empfehle dir die Buecher von Scott Meyers: "Effektiv C++ programmieren" und "Mehr effektiv C++ programmieren".



  • Simon2 schrieb:

    template <int DIM>
    Polygon<DIM>* f(Polygon<DIM> const& dummy);
    
    template <int DIM>
    Polygon<DIM>* f(int DIM) { return f(Polygon<DIM>); }
    

    Ich bezweifle dass das funktioniert, denn erstens hast du für zwei verschiedene Identifyer denselben Bezeichner gewählt (einmal den Templateparameter, einmal den Funktionsparameter), zweitens wird erst zur Laufzeit entschieden, welches Argument der Funktion übergeben wird, der Templateparameter muss aber schon zur Laufzeit feststehen.
    Das Ganze müsste daher eher mit einer Art Int2Type als Funktionsargument geschehen:

    template <int DIM>
    Polygon<DIM>* f(Int2Type<DIM> /*dummy*/);
    


  • pumuckl schrieb:

    ...Ich bezweifle dass das funktioniert, ...

    Sollte auch eher eine Ideenskizze sein (hätte ich wohl besser hingeschrieben).
    Außerdem war die Idee zur 2. Funktion eher ein "Spontanblitz" .... was wohl (wie so oft) unter die Kategorie "Der Erste aber nicht der Beste" fällt 😉

    Deine Kritikpunkte zielen im Wesentlichen auf mein zweites template ab, oder?...

    Gruß,

    Simon2.



  • Rock Lobster schrieb:

    ...die CPU ist auf Voll-Last...

    Das ist an sich ein gutes Zeichen.
    Die Frage ist eher, ob der Prozess insgesamt zu lange dauert ... und ob Deine ergriffenen Maßnahmen helfen.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Deine Kritikpunkte zielen im Wesentlichen auf mein zweites template ab, oder?...

    Jup.

    @Rock: wie schon gesagt wurde, und wie du auch richtig erkannt hast gibts zwei Möglichkeiten: die "Größe" (= Zahl der Ecken) jedes Polygons zur Compilezeit festlegen -> alles mit Templates, oder zur Laufzeit -> keine Templates, dafür dynamische Speicherverwaltung.


Anmelden zum Antworten