Template Vererbung - Funktion von Kind aufrufen



  • Hallo!

    Leider kann man keine virtual Template Methoden deklarieren. D.h. also dass ein Vorfahre keine kinderspezifische Versionen seiner Methoden aufrufen kann. Das ist überaus ärgerlich, denn so bin ich gezwungen dutzendweise den gleichen (langen) Code zu implementieren, wieder und wieder. Es sei denn es gibt da doch noch eine Lösung?!

    Ich schreibe Plugins für VTK (irgendeine VisualisierungsAPI halt). Diese Plugins leitet man von einer Klasse ab, die ich einfach mal "FilterPlugin" nenne (der wirklich Name ist so lang wie die Anmerkung hier). Hier soll man dann processImage(...) überschreiben.

    Da ich aber mit anderen Bildtypen arbeiten muss als dass was ich von processImage bekomme muss ich eine Konvertierung durchführen bevor ich mit der Bildbearbeitung loslege, und dafür muss ich mit Template Funktionen arbeiten.

    Wenn die Konvertierung abgeschlossen ist kommt der eigentliche Filter (applyFilter(...)), wovon ich seeehr viele Varianten schreiben möchte. Deswegen möchte ich diese Funktion gerne überschreiben. Dummerweise muss ich aber hier den Datentypen kennen, also ist das eben eine Template Funktion.

    Lange Rede, kurzer Sinn, ich versuche so etwas zu implementieren:

    class A : public FilterPlugin {
        public:
    	void processImage(vtkImageData*);	// Anfrage von Extern
    
        private:
    	void detectType(vtkImageData*);		// Datentyp ermitteln
    	template<ImageType>
    	ImageType* complicatedConversion(vtkImageData*); //aufwändig und groooß...
    
        protected:
    	template<ImageType>
    	virtual void applyFilter(ImageType *inputImage) = 0;
    }
    
    //////////////////////////////////////////////////////////////////
    // Extern übergibt A ein Bild zum Bearbeiten
    void A::processImage(vtkImageData* vtkInputImage) {
        //Typ erkennen und Bearbeitungskette starten
        detectType( vtkInputImage );
    }
    
    //nach Typerkennung dann applyFilter entsprechend aufrufen
    void A::detectType(vtkImageData* vtkInputImage)
    {
        scalarType = vtkInputImage->GetScalarType();	//z.B. VTK_DOUBLE
        if (scalarType == VTK_DOUBLE) {
    	typedef ImageType itk::Image< double, 2 >;	//Bildformat festlegen
    	// vtkInputImage zu ImageType konvertieren
    	ImageType myImage = complicatedConversion<ImageType>( vtkInputImage );
    	applyFilter<imageType>( myImage );
    
        } else if (scalarType == ...) {
    	...
        } ...
    }
    
    //////////////////////////////////////////////////////////////////
    // B braucht sich nicht mehr um den ganzen Konvertierungskram
    // zu kümmern und kann direkt mit dem schönen ImageType arbeiten
    // B braucht sich auch nicht darum kümmern, wann eine Anfrage kommt,
    // denn das macht ja schon A
    class B : public A {
        protected:
    	template<ImageType>
    	applyFilter(ImageType *inputImage);
    }
    
    template<ImageType>
    void B::applyFilter(ImageType *inputImage)
    {
        //hiervon jetzt dutzende von Varianten
    }
    

    ... was natürlich nicht geht. Ich sehe irgendwie keine andere Möglichkeit, als ab inklusive der Methode "detectType" den ganzen Kram mit zu den Kindern zu schleppen, weil ja hier die "Template-Kette" losgeht. Ich kann mir dann nur das processImage, also die Kommunikation mit Extern sparen.

    Damit könnte ich mich dann für das Projekt mit der größten Code-Redundanz bewerben. Seeehr wartungsfreundlich und unheimlich clever gestrickt... Und jeder einzelne Filter ist 8MB groß, ganz großes Kino 😞

    Hat da jemand eine Idee?



  • Nachdem ich mir nun 91 Mal mein eigenes Posting angesehen habe, ist mir immer noch nicht klar, wie ich das Problem lösen kann.

    Nach stundenlangem Gefrickel und vielen Versuchen, das Problem aufzudröseln bin ich denke ich an einem Punkt wo ich die Frage etwas anders formulieren kann.

    Kann ich den Typ einer Template Klasse zur Laufzeit festlegen, bevor ich sie tatsächlich mit new instantiiere? Das ist doch dynamisch und der Compiler weiß dann ja quasi aus der Methode in der die Instanziierung erfolgt (im Beispiel "setupBox()"), welche Varianten er so brauchen wird. Wie kann ich das realisieren? Ich denke da an folgendes:

    #ifndef XTK_BOX
    #define XTK_BOX
    
    template <typename ImageType>
    class XTKBox {
        private:
    	vtkImageData*	vtkImage;
    	ImageType*	itkImage;
        public:
    	xtkBox();
    
    	ImageType* 	getITKImage();	//will be called by custom filter
    	vtkImageData*	getVTKImage();	//will be called by parent
    	void setITKImage( ImageType* );
    };
    
    class MagicImageBox {
        // soll eine Instanz von XTKBox werden, aber Typ ist noch nicht klar
        XTKBox<?>	*meineBox;
    
        public:
    
    	void setupBox() {
    	    meineBox = new XTKBox<itk::image<char, 3> >;
    	}
    }
    #endif
    


  • Mit Templates bist du grundsätzlich an die Kompilierzeit gebunden. Wahrscheinlich läuft deine Lösung auf eine Kombination von dynamischer und statischer Polymorphie hinaus.

    // soll eine Instanz von XTKBox werden, aber Typ ist noch nicht klar
    XTKBox<?>    *meineBox;
    

    Dieses Fragezeichen ist eben das Problem. Wenn du den Typ zu diesem Zeitpunkt nicht festlegen kannst, hast du kein gültiges Template-Argument.

    Könnte nicht sowas funktionieren?

    class ImageBoxBase
    {
        // bietet Interface an
    };
    
    template <typename ImageType>
    class ImageBox : public ImageBoxBase
    {
        // konkrete Implementierung mit ImageType
    };
    
    class Magic
    {
        private:
            ImageBoxBase* myBox;
    
        public:
            void SetupBox()
            {
                myBox = new ImageBox<KonkretesTemplateArgument>;
            }
    };
    

    Virtuellen Destruktor und delete nicht vergessen. 😉



  • Vielen Dank, da wär ich nie drauf gekommen. Das sieht prinzipiell sehr gut aus, aber irgendwie schnaggelt mein Compiler da immernoch was nicht:

    /home/christian/programming/pv-plugins/Transformations/
    ivtkSampleFilter.cxx:22: error: "class XTKBoxBase"
     has no member named "getITKFromVTKImage"
    

    das betrifft folgende Zeile:

    XTKBoxBase *mBox = new XTKBox<ImageType>( vtkInput );
        ImageType* itkImage = mBox->getITKFromVTKImage();
    

    (Klar ich kann oben direkt eine XTKBox anlegen, aber die will ich eigentlich in der Vaterklasse des Filters anlegen, obiges ist nur zum Testzweck)

    Und meine schöne Box Klasse sieht jetzt so aus:

    #ifndef XTK_BOX
    #define XTK_BOX
    
    class XTKBoxBase {
        private:
    	vtkImageData*	vtkImage;
    
        public:
    	XTKBoxBase();
    	XTKBoxBase(vtkImageData* input) { vtkImage = input; }
    	//set vtkImageData
    	void setVTKImage(vtkImageData* input) { vtkImage = input; }
    	//two ways of getting a vtkImageData
    	vtkImageData* getVTKImage() { return vtkImage; }
    	virtual vtkImageData* getVTKFromITKImage() = 0;
    };
    
    template <typename ImageType>
    class XTKBox : public XTKBoxBase {
        private:
    	typedef itk::VTKImageImport<ImageType> ImageImportType;
    	typedef itk::VTKImageExport<ImageType> ImageExportType;
    	typename ImageImportType::Pointer	itkImporter;
    	typename ImageExportType::Pointer	itkExporter;
    
    	vtkImageImport*		vtkImporter;
    	vtkImageExport*		vtkExporter;
    
    	ImageType*	itkImage;
        public:
    	XTKBox() {};
    	XTKBox(vtkImageData*);
    
    	ImageType* 		getITKFromVTKImage();	//will be called by custom filter
    	virtual vtkImageData* 	getVTKFromITKImage();
    
    	void setITKImage( ImageType* );
    };
    #endif
    

    Sieht so aus als ob ich spezifische Kindfunktionen nicht einfach so aufrufen kann, selbst wenn ich der Base Instanz sage welche spezifische Kindklasse sie werden soll. Es sei denn ich definiere die Methode in der Base Klasse als virtual und überschreibe sie.

    HAAHAAAHAAAA :p Ich lach mich tot!

    Ich kann das vergessen oder? Naja, wenigstens ist jetzt alles aufgeräumter und - okay - dann muss eben jede Filterversion seinen eigenen Konvertierungscode mitschleppen...

    Vielen Dank für die Hilfe, hab zumindest einiges dazugelernt!



  • quaid schrieb:

    Das sieht prinzipiell sehr gut aus, aber irgendwie schnaggelt mein Compiler da immernoch was nicht:

    Du hast auch einmal getITKFromVTKImage() und einmal getVTKFromITKImage() . Das ist das Problem mit kryptischen Bezeichnern. 😉

    quaid schrieb:

    Ich kann das vergessen oder? Naja, wenigstens ist jetzt alles aufgeräumter und - okay - dann muss eben jede Filterversion seinen eigenen Konvertierungscode mitschleppen...

    Ich verstehe nicht ganz was du meinst. Wo genau hast du jetzt noch eine Codeduplizierung?



  • Hi,
    ich bin der meinung das Nexus den richtigen ansatz hat.
    Ich habe das so gelöst, dass ich noch den typ als unique ID als privatemember und eine cast routine in der (polymorphen) typenklasse mitschleppe.
    Das ist dann zwar mehr code, lohnt sich aber da dies dann auch dynamisch zur laufzeit genutzt werden kann.

    Sei gegrüßt



  • Hi Leute!

    Danke für die Antworten. Das ITKFromVTK und andersrum ist schon richtig so, die Funktion die er nicht findet ist ja auch in der Kindklasse definiert. Ein Freund von mir meinte jetzt, sowas geht dann nur mit einem dynamic_cast, das werd ich jetzt mal testen, bzw. erstmal verstehen was das ist.
    Es geht ja prinzipiell darum, das eine Instanz der BaseKlasse Stellvertreter einer Childklasse wird und dann eine Funktion aufrufen soll, die NUR in der Childklasse definiert wurde.

    Jaja, und ich wollte nur mal eben einen schönen praktischen Basisfilter zum ableiten schreiben und jetzt lern ich hier noch alle Kniffe der C++ Programmierung... 😃



  • quaid schrieb:

    Ein Freund von mir meinte jetzt, sowas geht dann nur mit einem dynamic_cast, das werd ich jetzt mal testen, bzw. erstmal verstehen was das ist.

    dynamic_cast ist zu vermeiden wo möglich. Vor kurzem habe ich dagegen argumentiert.

    quaid schrieb:

    Es geht ja prinzipiell darum, das eine Instanz der BaseKlasse Stellvertreter einer Childklasse wird und dann eine Funktion aufrufen soll, die NUR in der Childklasse definiert wurde.

    Hier steckt auch der Designfehler. Sowas kann man fast immer anders lösen, sodass diese Notwendigkeit gar nicht erst auftritt. Gibt es bei dir keine Möglichkeit, ein entsprechendes Interface (rein virtuelle Funktionen) in der Basisklasse einzurichten?



  • Das erfassen des ImageTypes ist leider nicht wegzukapseln, geht einfach nicht.

    Dynamic_Cast würde mir doch helfen, zumindest würde mir das vom Design her schon gefallen. Aber ich hab ja jetzt meine XTKBox<ImageType>, damit ist dann zumindest der ganze Konvertierungskram schön in einer separaten Template-Klasse weggekapselt.

    Leidet denn die Performance so sehr, wenn ich einmal dynamic-caste ??

    Kann natürlich trotzdem sein, dass ich da einen Denkfehler habe, wer weiß. Jedenfalls kann ich mich jetzt nicht mehr damit aufhalten da ich noch sehr viel anderes zu programmieren habe und dann auch irgendwann mal fertig werden wollte.

    Viele Dank nochmal an alle!



  • quaid schrieb:

    Leidet denn die Performance so sehr, wenn ich einmal dynamic-caste ??

    Das ist nicht das Hauptproblem (auch wenn gängige dynamic_cast -Implementierungen nicht besonders schnell sind). Falls es dich interessiert: In meinem Link steht eine Begründung.

    quaid schrieb:

    Kann natürlich trotzdem sein, dass ich da einen Denkfehler habe, wer weiß. Jedenfalls kann ich mich jetzt nicht mehr damit aufhalten da ich noch sehr viel anderes zu programmieren habe und dann auch irgendwann mal fertig werden wollte.

    Leider hat das Design oft unter Zeitdruck zu leiden. 😞

    Es ist ja nicht wahnsinnig tragisch, das einmal zu tun. Ich kenne dein Design auch zu wenig, um da konkrete Verbesserungsvorschläge zu bringen. Wichtig ist einfach, dass du dir Typ-Abfragen und dynamische Downcasts auf keinen Fall angewöhnst und als Standard-Herangehensweise verwendest. Es besteht nämlich nicht nur ein theoretisches Problem ("nicht sehr sauberes Design"), sondern kann tatsächlich einige Schwierigkeiten mit sich bringen, siehe Link.



  • Ich geh kaputt. :p Meine Template Klasse xvtkBox ist ja schön weggekapselt als eigene xvtkBox.h und xvtkBox.cxx. Doll doll. Bis auf dass der Compiler dann natürlich mal glatte 0 Varianten der Klasse erzeugt, jeweils eine für jeden zur Compilezeit bekannten ImageType... Der Linker streikt dann natürlich, weil es keinen passenden Konstruktor für meinen ImageType gibt.

    Die ImageTypes sind immer von dieser Art:

    itk::Image< itk::vector<double, x>, y >
    

    Für jede Dimension y, Anzahl Komponenten x (bei RGB z.B. 3) und 12 Skalartypen. Da kommt man auf 3*3*12 = 108 Varianten.

    Soll ich die jetzt alle als Spezialisierung da mit reinschreiben??!



  • quaid schrieb:

    Soll ich die jetzt alle als Spezialisierung da mit reinschreiben??!

    Ja, für das sind Spezialisierungen schliesslich da. 108 geht ja noch, du hättest es schlimmer treffen können.

    Natürlich nicht. :p
    Bei solch massiver Codeduplizierung gibts immer einen besseren Weg (und wenns im Notfall Präprozessor-Metaprogrammierung ist). Bei dir glaube ich aber, lässt sich mit Template-Metaprogrammierung einiges machen. Wahrscheinlich unter Einsatz von Typlisten. Das ist aber nicht ganz trivial... Kennst du zufällig das Buch "Modern C++ Design"?

    Aber wie sieht das Problem genau aus? Ich verliere langsam den Überblick. Am Anfang ging es noch darum, die Instanzen mit unterschiedlichem Templateargument polymorph anzusprechen, dann ging es um dynamisches Downcasten, und jetzt willst du Codegeneratoren? 😉

    Kannst du wenn möglich ein vereinfachtes Beispiel zeigen, das keine irrelevanten Details enthält? Also dein aktuelles Problem, soweit wie möglich reduziert (und nicht mehr zu viel kryptische Bezeichner :)).



  • Hi!

    Also das mit dem dynamic_cast und so hat sich erledigt. Mein Programm ist jetzt etwas anders (besser) aufgebaut. Was sehr schön ist, ist dass die XTKBox jetzt den ganzen Konvertierungskram erledigt und ich ihr sagen kann "Gib mir das ITK Bild / Mach aus dem VTK Bild ein ITK Bild / ..."

    Aber die Box ist halt ne Templateklasse und da sie in einer eigenen .h .cxx steht kann der Compiler natürlich nicht wissen, welcher Datentyp der ImageType denn mal werden wird. Deswegen streikt ja auch der Linker, compilieren tut das alles.
    Ich weiß aber, wie der Image Type aussieht, es sind eben die oben 108 beschriebenen Varianten.

    Die existierenden Filter in ITK (mit denen ich die Bilder bearbeite, VTK ist das "Visualization Toolkit") werden auch immer mit dem ImageType instanziiert, davon gibts also auch immer diese 108 Varianten:

    So ein Filter sieht z.B: so aus:

    itk::Image< char, 2 > ImageType; //2-dimensionales Bild, jeder Pixel als char
    
    itk::MedianImageFilter< ImageType > FilterType;
    FilterType myFilter = FilterType::New();
    myFilter->SetInput( imageReader->GetOutput() );
    ...
    

    Das ist immer wie eine Pipeline aufgebaut. Reader->Filter->Filter->...->Writer Und jeder Filter (Reader und Writer sind auch "Filter") muss eben immer mit diesem ImageType instanziiert werden...

    Wär echt klasse wenn ich da irgendwie meine 108 Varianten zur Compilezeit erzeugen könnte. Wär mir auch egal wie groß das Ding dann wird, immerhin ist es jetzt eine eigene schöne Klasse geworden.

    Ich würde mir liebend gerne in aller Ruhe ein Buch darüber anschauen, aber ich fürchte dafür hab ich keine Zeit. 😞
    Hier nochmal meine fantastische XTKBox, eine XTKBoxBase gibts jetzt garnicht mehr.

    #ifndef XTK_BOX
    #define XTK_BOX
    
    template <typename ImageType>
    class XTKBox {
        protected:
    	typedef itk::VTKImageImport<ImageType> ImageImportType;
    	typedef itk::VTKImageExport<ImageType> ImageExportType;
    	typename ImageImportType::Pointer	itkImporter;
    	typename ImageExportType::Pointer	itkExporter;
    
    	vtkImageImport*		vtkImporter;
    	vtkImageExport*		vtkExporter;
    
    	ImageType*	itkImage;
    	vtkImageData*	vtkImage;
        public:
            //hier jetzt die ganzen Konvertierungsmethoden
            //...getter und setter...
    };
    #endif
    

    Im Hauptprogramm geh ich dann hin und mache ein

    XTKBox<ImageType> *magicBox = new XTKBox<ImageType>( vtkInput );
        ImageType* itkImage = magicBox->getITKFromVTKImage();
    

    und schwupps hab ich auch schon aus dem VTK Bild ein ITK Bild gemacht und bin überglücklich und springe vor lauter Freude stundenlang im Dreieck. 🕶



  • Hab mich jetzt doch mit der Template Meta-Programmierung auseinandergesetzt. Diesbezüglich hab ich ein neues Topic eröffnet.

    Ansonsten funktioniert alles bestens! Die 108 Varianten hab ich dem Compiler beigebracht, indem ich in der XTKBox.cxx einfach mit meinen ja schon programmierten 3-Schritt Typgenerierungsfunktionen bestückt und für jeden ImageType dann eine Instanz erzeugt und alle Funktionen die es gibt davon aufgerufen (der compiliert doch glatt nur Funktionen, die auch verwendet werden! 😉 )

    Das ist natürlich nicht gerade die feinste Art, daher neuer Thread

    "Template Metaprogrammierung - Typvarianten erzeugen"


Anmelden zum Antworten