Polymorphe Liste erstellen, die Objekte der gleichen Basis-Klasse hält?

Pain-maker

Mitglied
Hi @ all!

(Das ist zwar ein Thema das schon zur genüge in Foren diskutiert wurde, aber zu meinem konkreten Fall hab ich einfach nichts passendes via Suchmaschinen gefunden...)

Ich bin gerade dabei ein Polymorphe Liste in ISO-C++ zu erstellen und bin nun mit meinem Latein am Ende...
Ich habe es bereits, dank Templates, geschafft eine Liste zu bauen, die Objekte der gleichen Basis-Klasse hält. Wenn ich nun aber versuche eins der Listen-Elemente zu kopieren (klonen) verliert es die Informationen des eigentlichen Typs und fällt auf die Basis-Klasse zurück.
Vielleicht mal zu Beginn der Quellcode:

PHP:
#include <iostream>
#include <conio.h>
#include <stdlib.h>
#include <list>

/**
 * DynList
 */
template<typename _Type> class DynEntry
{       
    public:
        _Type *data;

	public:
        // clone
		void clone()
		{
			this->data = new _Type(*data);
		}
};



/**
 * DynList
 */
template<typename _BaseType> class DynList
{
    protected:
        typedef typename DynEntry<_BaseType> ListEntry;
        std::list<ListEntry> data;

    public:
        // ~DynList
        virtual ~DynList()
        {
            this->freeMemory();
        }

    protected:
        // freeMemory
        void freeMemory()
        {
            std::list<ListEntry>::iterator iter;
            std::list<ListEntry>::iterator iterEnd = this->data.end();

            for(iter=this->data.begin(); iter!=iterEnd; iter++)
                delete (*iter).data;
        }

    public:
        // push
        template<typename _DerivedType> _DerivedType *push(_DerivedType *element)
        {
            DynEntry<_DerivedType> entryPolymorph;
            entryPolymorph.data = new _DerivedType(*element);

            ListEntry entry;
            entry.data = new _DerivedType(*element);

            //this->data.push_back(entry);          // Funktioniert, aber der Typ geht beim Klonen verloren
            this->data.push_back(entryPolymorph);   // Funktioniert nicht --> error C2664: 'void std::list<_Ty>::push_back(_Ty &&)' : cannot convert parameter 1 from 'DynEntry<_Type>' to 'DynEntry<_Type> &&'
            return dynamic_cast<_DerivedType*>(this->data.back().data);
        }

        // front
        _BaseType *front()
        {
            return this->data.front();
        }
        
        // back
        _BaseType *back()
        {
            return this->data.back();
        }

        // clone
        _BaseType *clone(_BaseType *ptr)
        {
            std::list<ListEntry>::iterator iter;
            std::list<ListEntry>::iterator iterEnd = this->data.end();

            for(iter=this->data.begin(); iter!=iterEnd; iter++)
            {
                if((*iter).data == ptr)
                {
                    this->data.push_back(*iter);
                    this->data.back().clone();    // Ohne funktioniert, aber dann existiert das gehaltene Objekt nur einmal
					return (this->data.back().data);
                }
            }
            return NULL;
        }
};



/**
 * Vehicle
 */
class Vehicle
{
    protected:
        char name[32];
    
    public:
        Vehicle()
        {
            strcpy_s(this->name, "Vehicle");
        }
        virtual ~Vehicle()
        {
        }
};



/**
 * Car
 */
class Car : public Vehicle
{
    protected:
        int numTires;

    public:
        Car()
        {
            strcpy_s(this->name, "Car");
            this->numTires = 4;
        }
};



/**
 * NuclearSubmarine
 */
class NuclearSubmarine : public Vehicle
{
    protected:
        int numBombs;
    
    public:
        NuclearSubmarine()
        {
            strcpy_s(this->name, "NuclearSubmarine");
            this->numBombs = 10;
        }
};



/**
 * main
 */
void main()
{
	DynList<Vehicle> myList;       // "Objekt-Speicher"
    std::list<Car*>  refList;	   // Liste die Daten aus dem "Objekt-Speicher" hält

	// 2 Test-Objekte erzeugen
    NuclearSubmarine car1;
    Car              car2;

	// Jeweils eine Kopie auf den "Objekt-Speicher" schieben und den Pointer speichern
    NuclearSubmarine *ret1 = myList.push(&car1);
    Car              *ret2 = myList.push(&car2);

	// Objekt kopieren (klonen)
    Vehicle          *test1 = myList.clone(ret1);
    NuclearSubmarine *test2 = (NuclearSubmarine*)test1;
    Car              *test3 = (Car*)test1;

	// Auf eine weitere Liste schieben, welche die Daten (die Pointer auf die eigentlichen Daten) halten soll
    refList.push_back((Car*)ret1);
    refList.push_back((Car*)ret2);
    refList.push_back((Car*)test1);
    refList.push_back((Car*)test2);
	
	// Break here!
    _getch();
}

Ich versuche hier einen neutralen Speicher (DynList<Vehicle> myList) zu schaffen, der die Objekte hält. Die Pointer auf diese Objekte möchte ich dann auf weitere Listen verteilen können.
Mit der Zeile in DynList:: push()...
PHP:
this->data.push_back(entry)
...funktioniert das auch soweit ganz gut, solange bis versuche eines der Objekte zu kopieren (via new).
Ist ja auch ganz logisch da DynListEntry<_Type> mit _BaseType kompiliert wird. Daher kam ich auf die für mich logischste Lösung: Nämlich:
PHP:
this->data.push_back(entryPolymorph);
Dann wird DynListEntry<_Type> mit _DerivedType kompiliert und würde somit den richtigen Typen kennen.
Allerdings erhalte ich dann den Compiler-Error:
Code:
error C2664: 'void std::list<_Ty>::push_back(_Ty &&)' : cannot convert parameter 1 from 'DynEntry<_Type>' to 'DynEntry<_Type> &&'

Ich bin doch so kurz davor...
Was mache ich noch falsch? Kann mir jemand weiterhelfen?

BTW: Das dem Code noch das Exception-Handling fehlt, weiß ich ;)

Mfg Pain-maker
 
Zuletzt bearbeitet:
Hi.

Der Fehler ist richtig, DynEntry<X> ist ein völlig anderer Typ als DynEntry<Y> (X != Y).

Evtl. solltest du dir mal Boost.Any anschauen.

Gruß
 
Klar ist das ein anderer Typ, aber X ist von Y abgeleitet, also muss es da doch noch irgendeinen Weg geben, oder? Mein Problem ist, dass ich den Typen irgendwie an die Daten binden will, diesen nach dem push() aber eigentlich nicht mehr kenne.

Das ist mit boost ist gut, aber ich würde gerne vorläufig darauf verzichten.
 
Klar ist das ein anderer Typ, aber X ist von Y abgeleitet, also muss es da doch noch irgendeinen Weg geben, oder?
Nein. Das wird so nicht funktionieren.
Mein Problem ist, dass ich den Typen irgendwie an die Daten binden will, diesen nach dem push() aber eigentlich nicht mehr kenne.

Das ist mit boost ist gut, aber ich würde gerne vorläufig darauf verzichten.
Warum nimmst du denn nicht gleich eine std::list von Zeigern? (oder einen Kontainer der Boost Pointer Container Library?)

Gruß
 
Nun das war mein erster Ansatz:

PHP:
#include <iostream>
#include <conio.h>
#include <stdlib.h>
#include <list>

/**
 * DynList
 */
template<typename _BaseType> class DynList
{
    protected:
        std::list<_BaseType*> data;

    public:
        // ~DynList
        virtual ~DynList()
        {
            this->freeMemory();
        }

    protected:
        // freeMemory
        void freeMemory()
        {
            std::list<_BaseType*>::iterator iter;
            std::list<_BaseType*>::iterator iterEnd = this->data.end();

            for(iter=this->data.begin(); iter!=iterEnd; iter++)
                delete (*iter);
        }

    public:
        // push
        template<typename _DerivedType> _DerivedType *push(_DerivedType *element)
        {
            this->data.push_back(new _DerivedType(*element));
            return dynamic_cast<_DerivedType*>(this->data.back());
        }

        // front
        _BaseType *front()
        {
            return this->data.front();
        }
        
        // back
        _BaseType *back()
        {
            return this->data.back();
        }

        // clone
        _BaseType *clone(_BaseType *ptr)
        {
            std::list<_BaseType*>::iterator iter;
            std::list<_BaseType*>::iterator iterEnd = this->data.end();

            for(iter=this->data.begin(); iter!=iterEnd; iter++)
            {
                if((*iter) == ptr)
                {
                    this->data.push_back(*iter);
                    // Hier fehlt ein new
                    return (this->data.back());
                }
            }
            return NULL;
        }
};



/**
 * Vehicle
 */
class Vehicle
{
    protected:
        char name[32];
    
    public:
        Vehicle()
        {
            strcpy_s(this->name, "Vehicle");
        }
        virtual ~Vehicle()
        {
        }
};



/**
 * Car
 */
class Car : public Vehicle
{
    protected:
        int numTires;

    public:
        Car()
        {
            strcpy_s(this->name, "Car");
            this->numTires = 4;
        }
};



/**
 * NuclearSubmarine
 */
class NuclearSubmarine : public Vehicle
{
    protected:
        int numBombs;
    
    public:
        NuclearSubmarine()
        {
            strcpy_s(this->name, "NuclearSubmarine");
            this->numBombs = 10;
        }
};



/**
 * main
 */
void main()
{
	DynList<Vehicle> myList;       // "Objekt-Speicher"
    std::list<Car*>  refList;	   // Liste die Daten aus dem "Objekt-Speicher" hält

	// 2 Test-Objekte erzeugen
    NuclearSubmarine car1;
    Car              car2;

	// Jeweils eine Kopie auf den "Objekt-Speicher" schieben und den Pointer speichern
    NuclearSubmarine *ret1 = myList.push(&car1);
    Car              *ret2 = myList.push(&car2);

	// Objekt kopieren (klonen)
    Vehicle          *test1 = myList.clone(ret1);
    NuclearSubmarine *test2 = (NuclearSubmarine*)test1;
    Car              *test3 = (Car*)test1;

	// Auf eine weitere Liste schieben, welche die Daten (die Pointer auf die eigentlichen Daten) halten soll
    refList.push_back((Car*)ret1);
    refList.push_back((Car*)ret2);
    refList.push_back((Car*)test1);
    refList.push_back((Car*)test2);
	
	// Break here!
    _getch();
}

Aber dann hatte ich genau das Problem, dass ich zwar den Listeneintrag klonen, aber nicht das Objekt kopieren konnte.
WIe kann ich denn jetzt in DynList::clone() ein new auf einen unbekannten Typen machen?

Das war der Auslöser das ich noch ein Template für jeden Eintrag gemacht habe, um den Typen an den Eintrag zu binden, was ja leider auch fehlgeschlagen ist :(
Ich habe auch schon sowas wie template<_Type*> versucht, aber das geht natürlich nicht...

Auf Boost würde ich gerne verzichten, da ich auch gerne Ohne eine Lösung finden möchte.
Any ideas?

Mfg Pain-maker



//EDIT:
Okay, weiß nich warum ich nich eher mal daran gedacht hab, aber ich hab jetzt ne Lösung die geht und mich einigermaßen zufrieden stellt:
PHP:
#include <iostream>
#include <conio.h>
#include <stdlib.h>
#include <list>

/**
 * Cloneable
 */
class Cloneable
{
	public:
		// ~Cloneable
		virtual ~Cloneable()
		{
		}

	public:
		// clone
		virtual Cloneable *clone()
        {
			return new Cloneable(*this); 
        }
};

/**
 * DynList
 */
template<typename _BaseType> class DynList
{
    protected:
        std::list<_BaseType*> data;

    public:
        // ~DynList
        virtual ~DynList()
        {
            this->freeMemory();
        }

    protected:
        // freeMemory
        void freeMemory()
        {
            std::list<_BaseType*>::iterator iter;
            std::list<_BaseType*>::iterator iterEnd = this->data.end();

            for(iter=this->data.begin(); iter!=iterEnd; iter++)
                delete (*iter);
        }

    public:
        // push
        template<typename _DerivedType> _DerivedType *push(_DerivedType *element)
        {
			// Muss von Cloneable und _BaseType abgeleitet sein
			if((Cloneable*)element == dynamic_cast<Cloneable*>(element) && (_BaseType*)element == dynamic_cast<_BaseType*>(element))
			{	
        		this->data.push_back(dynamic_cast<_BaseType*>(new _DerivedType(*element)));
        		return dynamic_cast<_DerivedType*>(this->data.back());
			}
			return NULL;
        }

        // front
        _BaseType *front()
        {
            return this->data.front();
        }
        
        // back
        _BaseType *back()
        {
            return this->data.back();
        }

        // clone
        _BaseType *clone(_BaseType *ptr)
        {
            std::list<_BaseType*>::iterator iter;
            std::list<_BaseType*>::iterator iterEnd = this->data.end();

            for(iter=this->data.begin(); iter!=iterEnd; iter++)
            {
                if((*iter) == ptr)
                {
                    this->data.push_back(*iter);
					this->data.back() = this->data.back()->clone();
					return (this->data.back());
                }
            }
            return NULL;
        }
};



/**
 * Vehicle
 */
class Vehicle : public Cloneable
{
    protected:
        char name[32];
    
    public:
		// Vehicle
        Vehicle()
        {
            strcpy_s(this->name, "Vehicle");
        }

		// ~Vehicle
        virtual ~Vehicle()
        {
        }

	public:
		// clone
		virtual Vehicle *clone()
        {
			return new Vehicle(*this); 
        }
};



/**
 * Car
 */
class Car : public Vehicle
{
    protected:
        int numTires;

    public:
		// Car
        Car()
        {
            strcpy_s(this->name, "Car");
            this->numTires = 4;
        }
	
	public:
		// clone
		virtual Car *clone()
        {
			return new Car(*this); 
        }
};



/**
 * NuclearSubmarine
 */
class NuclearSubmarine : public Vehicle
{
    protected:
        int numBombs;
    
    public:
		// NuclearSubmarine
        NuclearSubmarine()
        {
            strcpy_s(this->name, "NuclearSubmarine");
            this->numBombs = 10;
        }
	
	public:
		// clone
		virtual NuclearSubmarine *clone()
        {
			return new NuclearSubmarine(*this); 
        }
};

  

/**
 * main
 */
void main()
{
	DynList<Vehicle> myList;       // "Objekt-Speicher"
    std::list<Car*>  refList;	   // Liste die Daten aus dem "Objekt-Speicher" hält

	// 2 Test-Objekte erzeugen
    NuclearSubmarine car1;
    Car              car2;

	// Jeweils eine Kopie auf den "Objekt-Speicher" schieben und den Pointer speichern
    NuclearSubmarine *ret1 = myList.push(&car1);
    Car              *ret2 = myList.push(&car2);

	// Objekt kopieren (klonen)
    Vehicle          *test1 = myList.clone(ret1);
    NuclearSubmarine *test2 = (NuclearSubmarine*)test1;
    Car              *test3 = (Car*)test1;

	// Auf eine weitere Liste schieben, welche die Daten (die Pointer auf die eigentlichen Daten) halten soll
    refList.push_back((Car*)ret1);
    refList.push_back((Car*)ret2);
    refList.push_back((Car*)test1);
    refList.push_back((Car*)test2);
	
	// Break here!
    _getch();
}

Alle Objekte, die jetzt auf die Liste geschoben werden, müssen Cloneable sein. Dadurch implementieren sie die Methode clone().
Wenn ich nun einen Listeneintrag kopiere, wird einfach die clone-Methode des gehaltenen Objekts aufgerufen, welches seinen Typen kennt. (Sofern es clone() überschrieben hat)
Die Liste lehnt auch (meinen Tests nach) zuverlässig Objekte ab, die weder Cloneable, noch _BaseType implementieren.

Irgendwelche Kommentare zu der Lösung?!
Die Frage, ob ich nicht irgendwie anders den Typen an das Objekt binden kann (via Template?!) steht aber nach wie vor im Raum.
Am Besten wäre ne Lösung, bei der noch Cloneable wegfallen würde... Also wenn jemand noch ne Idee hat: Her damit :D (Ohne Boost oder TR1 bestenfalls)

Mfg Pain-maker
 
Zuletzt bearbeitet:
Aber dann hatte ich genau das Problem, dass ich zwar den Listeneintrag klonen, aber nicht das Objekt kopieren konnte.
WIe kann ich denn jetzt in DynList::clone() ein new auf einen unbekannten Typen machen?
Das kannst du von "außen" nicht erreichen, da Typen keine Bürger 1. Klasse in C++ sind. Die clone() Methode sollte Teil der Klassen sein die du dort verwalten willst. Siehe z.B. http://www.boost.org/doc/libs/1_43_0/libs/ptr_container/doc/tutorial.html#cloneability
Auf Boost würde ich gerne verzichten, da ich auch gerne Ohne eine Lösung finden möchte.
Warum das denn? :confused:

Alles was du in den nächsten Jahren schreiben würdest wäre nicht annähernd so getestet und durchdacht wie die Boost Bibliotheken. Mal ganz abgesehen von dem Aufwand der eigentlich völlig umsonst wäre...

Gruß
 
Zurück