Objekttyp in C++ zur Laufzeit ermitteln

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Antworten
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Goderion »

Hallo.

Ich benutze in meinem Programm für fast alle Objekte folgendes Interface (sehr ähnlich wie IUnknown von OLE/COM):

Code: Alles auswählen

class InterfaceObjectBase
{
public:
	virtual Void AddRef(Void) const = 0;
	virtual Void Release(Void) const = 0;

	virtual UInt32 GetObjectTypeId(Void) const
	{
		return 0;
	}

	virtual const WChar* GetObjectTypeName(Void) const
	{
		return L"";
	}
};
Aktuell implementiere ich in jeder Klasse, wo ich den Objekttypen benötige, die Funktion GetObjectTypeId.
Gibt es da keine Möglichkeit, das Ganze so zu gestalten, dass man nicht für jede Klasse die nötige Funktion (GetObjectTypeId/GetObjectTypeName) implementieren muss?

Ich brauche diese Funktionalität für mehrere Sachen.
1. Ich habe eine grafische Benutzeroberfläche, wo ich jedem Fenster (ClassGuiWindow) eine Fenster-Klasse (ClassGuiWindowClass) zuweisen kann. Es gibt Situationen, wo ich z.B. alle Kind-Fenster von einem Fenster durchgehen muss um die Fenster mit einer speziellen Fenster-Klasse weiter zu behandeln.
2. Beim Drag&Drop kann auch ein Object (InterfaceObjectBase*) genutzt werden. ich muss aber während dem "Draggen" und dann beim "Droppen" natürlich wissen, was das für ein Objekt ist.

EDIT: Ich habe mich schon gefragt, ob das wirklich hier her gehört, aber da es speziell für C++ ist, gehört es wohl in "Programmiersprachen, Quelltext und Bibliotheken", daher bitte verschieben.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Krishty »

inb4 „OOP geht anders“ ;)

Du kannst typeid(obj) benutzen. Das spuckt dir für jeden Typ eine Instanz von std::type_info aus. Der Typ kann nicht viel, aber es ist garantiert, dass die type_info-Instanz jedes Typs eine einzigartige Adresse hat. Das kannst du dir so zunutze machen:

  if(&typeid(window) == &typeid(ClassGuiWindow)) {
    // …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
joggel

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von joggel »

Ich weiß nicht ob ich das recht verstanden habe, aber es gibt doch typeid().
https://msdn.microsoft.com/de-de/library/fyf39xec.aspx

Darüber kommst Du auch an den Namen heran...
Aber wie gesagt: ich weiß nicht ob ich das recht verstanden habe.

[Edit]
Dieser Kristhy war wieder schneller....
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Schrompf »

Aber trotz Krishtys initialer Bemerkung: OOP geht anders! Du willst eigentlich *nicht* in jeder Klasse hardcoded nen Referenzcounter drin haben. Das geht mit std::shared_ptr stressfrei, erfordert keine virtuellen Funktionsaufrufe und ist vor allem nicht von Hand, kann also nicht vergessen werden. Der Code, den Du da gepostet hast, riecht in der Tat nach "letztes Jahrzehnt" wie halt COM und Comsorten.

Ich hatte sowas auch mal drin vor langer Zeit. War der Hass zu warten. Irgendwann hab ich mir die Zeit genommen und habe es aus x hundert Klassen rausoperiert und durch shared_ptr/dynamic_cast/typeid ersetzt. Der Code wurde besser.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Goderion »

Vielen Dank für die Antworten.

typeid hilft leider nicht weiter, sobald Vererbung im Spiel ist.

Code: Alles auswählen

	InterfaceTest* pTest = new ClassTest();

	if (typeid(pTest) == typeid(InterfaceTest))
	{
		// ja
	}

	if(typeid(pTest) == typeid(ClassTest))
	{ 
		// nein
	}
shared_ptr macht doch im Prinzip das Gleiche, nur wird dort der Referenz-Zähler in einer extra Struktur/Speicherbereich verwaltet. Oder habe ich da was falsch verstanden?

Ich benutze auch fast nur SmartPointer, bzw. WeakPointer, die automatisch AddRef und Release aufrufen. Den Referenzzähler selber zu verwaltet wäre in der Tat Wahnsinn!
Die Implementation von AddRef und Release existiert auch nur einmal, ich muss nur für jede Klasse eine Delete-Funktion implementieren.
Ich habe alle new -und delete operatoren überschrieben, um sicherzustellen, dass am Ende vom Prozess nix über bleibt (Speicherleck).
Ich glaube auch, dass die virtuellen Funktionen AddRef und Release vom Compiler weg-optimiert werden, da diese nur einmalig implementiert sind.

Ich vermute shared_ptr wäre für mich auch kontraproduktiv. Ich habe teilweise 500.000 bis 1.000.000 Objekte, die ich über einen eigenen Speichermanager erstelle (Segregated Memory),
würde die Verwendung von shared_ptr nicht für jedes dieser Objekte eine extra Struktur/Speicher anfordern?
Dazu kommt noch, dass viele Objekte (je nach Situation 1000 bis 10000 pro Sekunde) zerstört und neu erstellt werden.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Schrompf »

Der std::shared_ptr würde einen Verwaltungsblock pro Objekt anlegen, wenn man ihn dazu zwingt. std::make_shared() allokiert dagegen nur einmal und konstruiert Objekt und Verwaltungsblock dann in-place. Dafür kann der shared_ptr<> halt alles verwalten, nicht nur Objekte, die von einer Basisklasse ableiten. Wenn Du wirklich so absurd viele Allokationen pro Sekunde hast, kann der Aufwand für die notwendigen Indirektionen bei Aufruf virtueller Funktionen tatsächlich messbar werden. Und Du kannst ja mal mit dem Debugger im Release nachschauen, ob der Compiler AddRef()/Release() *wirklich* wegoptimieren kann... nach meinem Wissen kann er das nicht.

In Deinem Beispiel willst Du übrigens dynamic_cast<> machen. Tests auf konkrete Klassen sind zwar gelegentlich nötig, die lehne ich nicht so militant ab wie andere Leute. Aber allgemein ist es doch ein Hinweis, nochmal darüber nachzudenken, was man eigentlich erreichen will.

Nuja, mach, was Du für richtig hältst.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Krishty »

Goderion hat geschrieben:typeid hilft leider nicht weiter, sobald Vererbung im Spiel ist.

Code: Alles auswählen

	InterfaceTest* pTest = new ClassTest();

	if (typeid(pTest) == typeid(InterfaceTest))
	{
		// ja
	}

	if(typeid(pTest) == typeid(ClassTest))
	{ 
		// nein
	}
Doch! Du vergleichst die Typen der Zeiger – du musst aber die Typen der Objekte dahinter vergleichen ;) if(typeid(*pTest) == typeid(ClassTest))
Schrompf hat geschrieben:Comsorten
Ich lachte :D
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Schrompf »

Nee, er meinte "Vererbung". Und typeid(Derived) ist nunmal != typeid(Base)
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Goderion »

Krishty hat geschrieben:Doch! Du vergleichst die Typen der Zeiger – du musst aber die Typen der Objekte dahinter vergleichen ;) if(typeid(*pTest) == typeid(ClassTest))
Funktioniert! Aber erst nachdem ich in ClassTest eine virtuelle Funktion hatte, davor verhielt es sich wie ohne *. DANKE SEHR!
Schrompf hat geschrieben:Der std::shared_ptr würde einen Verwaltungsblock pro Objekt anlegen, wenn man ihn dazu zwingt. std::make_shared() allokiert dagegen nur einmal und konstruiert Objekt und Verwaltungsblock dann in-place.
Die Objekte müssen vom eigenen Speichermanager erstellt werden, alles andere ist zu langsam und lässt sich zu schwer überwachen, daher müsste ich den shared_ptr so nutzen, das er pro Objekt einen Verwaltungsblock separat erzeugt, was ebenfalls inakzeptabel ist.
Könnte ich den Verwaltungsblock vom shared_ptr irgendwie vom Speichermanager erzeugen lassen?
Schrompf hat geschrieben:Dafür kann der shared_ptr<> halt alles verwalten, nicht nur Objekte, die von einer Basisklasse ableiten.
Für meine Bedürfnisse quasi nutzlos. Ich benutze nur Objekte/Klassen, die von dieser Basisklasse ableiten. Objekte/Speicher aus anderen Bibliotheken (WinAPI, DirectX) sind alle nochmal extra in einer eigenen Klasse gekapselt. Ist aufwändig, macht die Sache aber extrem übersichtlich und sicher.
Schrompf hat geschrieben:Wenn Du wirklich so absurd viele Allokationen pro Sekunde hast, kann der Aufwand für die notwendigen Indirektionen bei Aufruf virtueller Funktionen tatsächlich messbar werden. Und Du kannst ja mal mit dem Debugger im Release nachschauen, ob der Compiler AddRef()/Release() *wirklich* wegoptimieren kann... nach meinem Wissen kann er das nicht.
Ne, macht er auch nicht. AddRef und Release gibt es auch jeweils zwei mal.
Ich habe es mal getestet, wie lange es dauert, eine Million mal, AddRef und Release aufzurufen:
Mit ObjectValidation:
8244 µs
8259 µs
8238 µs
8257 µs
Ohne ObjectValidation:
4247 µs
4325 µs
4287 µs
4283 µs

Ich schätze mal, dass das Aufrufen der virtuellen Funktion davon ca. 2000 µs braucht, also 2 ms für 2.000.000 Aufrufe. Das wäre dann bei mir bei 10.000 Objekten ca. 200 µs (AddRef+Release) pro Sekunde, damit kann ich leben. ;-)
Schrompf hat geschrieben:In Deinem Beispiel willst Du übrigens dynamic_cast<> machen. Tests auf konkrete Klassen sind zwar gelegentlich nötig, die lehne ich nicht so militant ab wie andere Leute. Aber allgemein ist es doch ein Hinweis, nochmal darüber nachzudenken, was man eigentlich erreichen will.
Mmmmh... ich weiß was du meinst, glaube ich, aber für diese Situation fällt mir keine andere Lösung ein. Wie soll ich z.B. eine Zwischenablage realisieren, in der man alles mögliche speichern kann? In der Zwischenablage kann Text sein, ein Bild oder eine Liste von Objekten.
Ohne diese Allgemein-Lösung müsste ich an jeder Stelle, die mehrere Formate/Objekttypen unterstützt, für jede Situation/Objekttyp etwas extra programmieren, und das fände ich viel umständlicher.

Spricht denn eigentlich was dagegen seine eigenen SmartPointer und BasisKlassen zu benutzen? Ich sehe jetzt im shared_ptr für mich keine Vorteile im Vergleich zu einem eigenen SmartPointer.
Den Performance-Verlust durch die virtuellen AddRef/Release finde ich noch akzeptabel und die Notwendigkeit von einer Basisklasse zu erben ist bei mir so oder so gegeben.

EDIT: sepErat...
Gene
Beiträge: 25
Registriert: 22.05.2003, 11:26

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Gene »

Krishty hat geschrieben:inb4 „OOP geht anders“ ;)

Du kannst typeid(obj) benutzen. Das spuckt dir für jeden Typ eine Instanz von std::type_info aus. Der Typ kann nicht viel, aber es ist garantiert, dass die type_info-Instanz jedes Typs eine einzigartige Adresse hat. Das kannst du dir so zunutze machen:

  if(&typeid(window) == &typeid(ClassGuiWindow)) {
    // …
Anmerkung: das ist nicht ganz richtig. So weit ich weiß reicht das vergleichen der Adressen nicht aus. Es müssen die Hash-Codes miteinander verglichen werden:
if(typeid(window).hash_code() == typeid(ClassGuiWindow).hash_code()) {

Siehe Anmerkung ganze unten:
http://en.cppreference.com/w/cpp/language/typeid
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Krishty »

Gene: Stimmt; danke! Da war meine Quelle veraltet (dürfte noch eines von Alexandrescus frühen Werken gewesen sein). Ich schätze, die Änderung soll dynamische Bibliotheken besser abdecken (da entsteht schnell eine Adressdiskrepanz, wenn nicht die Type Infos aller Module miteinander abgeglichen werden), und da ist ein Hash die viel bessere Methode.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von dot »

Sorry, aber wenns sonst niemand tut: OOP geht anders.
Goderion hat geschrieben:Ich benutze in meinem Programm für fast alle Objekte folgendes Interface (sehr ähnlich wie IUnknown von OLE/COM):
wieso?
Goderion hat geschrieben:Gibt es da keine Möglichkeit, das Ganze so zu gestalten, dass man nicht für jede Klasse die nötige Funktion (GetObjectTypeId/GetObjectTypeName) implementieren muss?
Es geht sogar noch besser: Man gestaltet das Ganze so, dass man überhaupt kein GetTypeIrgendwas() braucht.
Goderion hat geschrieben:1. Ich habe eine grafische Benutzeroberfläche, wo ich jedem Fenster (ClassGuiWindow) eine Fenster-Klasse (ClassGuiWindowClass) zuweisen kann. Es gibt Situationen, wo ich z.B. alle Kind-Fenster von einem Fenster durchgehen muss um die Fenster mit einer speziellen Fenster-Klasse weiter zu behandeln.
Inwiefern hilt die Abfrage des konkreten Objekttyps dabei?
Goderion hat geschrieben:2. Beim Drag&Drop kann auch ein Object (InterfaceObjectBase*) genutzt werden. ich muss aber während dem "Draggen" und dann beim "Droppen" natürlich wissen, was das für ein Objekt ist.
https://en.wikipedia.org/wiki/Double_dispatch
NytroX
Establishment
Beiträge: 358
Registriert: 03.10.2003, 12:47

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von NytroX »

Funktioniert! Aber erst nachdem ich in ClassTest eine virtuelle Funktion hatte
Oha, kann es sein, dass dein Destructor in der Basisklasse/dem Interface dann nicht virtual ist?

http://stackoverflow.com/questions/4612 ... estructors
be aware that deleting a base class pointer when there is no virtual destructor will result in undefined behavior.
http://stackoverflow.com/questions/3009 ... estructors
There is no need to use a virtual destructor when any of the below is true:

No intention to derive classes from it
No instantiation on the heap
No intention to store in a pointer of a superclass
Soweit ich sehe, machst du alles 3:
InterfaceTest* pTest = new ClassTest();
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Goderion »

Vielen Dank für die Antworten!
dot hat geschrieben:Sorry, aber wenns sonst niemand tut: OOP geht anders.
Wenn ich das richtig verstanden habe, mache ich das auch so. Ich habe nur sehr wenige Stellen, wo ich z.B. Mehrfachvererbung nutze/benötige, wodurch dann aber keine bestehenden Funktionen verändert/überschrieben werden.
dot hat geschrieben:
Goderion hat geschrieben:Ich benutze in meinem Programm für fast alle Objekte folgendes Interface (sehr ähnlich wie IUnknown von OLE/COM):
wieso?
Ursprünglich habe ich das nur wegen dem Referenzzähler gemacht, mittlerweile habe ich aber weitere Funktionen implementiert, die mir das Überwachen und Prüfen der Objekte vereinfachen.
dot hat geschrieben:
Goderion hat geschrieben:Gibt es da keine Möglichkeit, das Ganze so zu gestalten, dass man nicht für jede Klasse die nötige Funktion (GetObjectTypeId/GetObjectTypeName) implementieren muss?
Es geht sogar noch besser: Man gestaltet das Ganze so, dass man überhaupt kein GetTypeIrgendwas() braucht.
Bis jetzt habe ich das auch immer geschafft, diese Funktion ausschließlich für Analyse-Zwecke zu nutzen, aber für mein aktuelles Vorhaben/Problem weiß ich nicht, wie ich das sinnvoll ohne diese Funktion lösen soll.
dot hat geschrieben:
Goderion hat geschrieben:1. Ich habe eine grafische Benutzeroberfläche, wo ich jedem Fenster (ClassGuiWindow) eine Fenster-Klasse (ClassGuiWindowClass) zuweisen kann. Es gibt Situationen, wo ich z.B. alle Kind-Fenster von einem Fenster durchgehen muss um die Fenster mit einer speziellen Fenster-Klasse weiter zu behandeln.
Inwiefern hilt die Abfrage des konkreten Objekttyps dabei?
Wie soll ich sonst die Fenster ermitteln, die z.B. gerade die Fenster-Klasse zum Darstellen von Container-Inhalten (Truhe, Beutel, Schrank) benutzen?
Ich will z.B. durch einen Doppelklick auf der Karte den Inhalt von einem Schrank anzeigen. Bevor ich ein neues Fenster erstelle, durchsuche ich die bereits vorhandenen Fenster, ob der Schrank nicht schon angezeigt wird.
Ich könnte der Fenster-Klasse einfach eine virtuelle Funktion mitgeben, womit man das prüfen könnte, und nur die betroffenen Fenster-Klassen implementieren das.
Aber wenn ich das mache, habe ich am Ende hunderte virtueller Funktionen, was ich sehr unübersichtlich finden würde. Ah... jetzt habe ich vielleicht ein Idee, da muss ich erstmal drüber nachdenken. ;-)
dot hat geschrieben:
Goderion hat geschrieben:2. Beim Drag&Drop kann auch ein Object (InterfaceObjectBase*) genutzt werden. ich muss aber während dem "Draggen" und dann beim "Droppen" natürlich wissen, was das für ein Objekt ist.
https://en.wikipedia.org/wiki/Double_dispatch
Mal ein Beispiel:
Ich habe eine Karte offen. In der Karte werden 2 Truhen und 2 Charakter jeweils per Fenster mit entsprechender Fenster-Klasse angezeigt. Dazu ist noch eine Minimap offen und ein Textfenster.
Jetzt gehe ich mit der Maus in der Karte auf z.B. einen Helm und fange an den per Drag&Drop zu bewegen.
Wenn ich den Helm über ein Fenster ziehe, werden dadurch entsprechende Funktionen in der Fenster-Klasse aufgerufen.
Die Fenster-Klasse muss jetzt wissen, was ich da gerade mit der Maus bewege, um entsprechend reagieren zu können.
Auf der Karte wird der Helm z.B. wie auch beim Bewegen von Icons auf dem Desktop an der Maus-Position leicht transparent angezeigt. Gehe ich damit allerdings über das Textfenster oder Minimap, erscheint eine Warnung per Tooltip. Im Charakter-Fenster kommt es dann z.B. darauf an, über welchen Ausrüstungs-Slot ich den Helm bewege.

Spontan fallen mir nur Möglichkeiten ein, ohne dabei den Objekttyp zu erfragen, die viel zu umständlich sind und/oder das Ganze unübersichtlich machen.
NytroX hat geschrieben:
Funktioniert! Aber erst nachdem ich in ClassTest eine virtuelle Funktion hatte
Oha, kann es sein, dass dein Destructor in der Basisklasse/dem Interface dann nicht virtual ist?
Im Basisinterface gibt es folgende Funktion:

Code: Alles auswählen

virtual Void Delete(Void) = 0;
Jedes Objekt kümmert sich dann um die eigene Löschung, was einen virtuellen Destruktor überflüssig macht.
Am Anfang hat mich das etwas genervt, für jedes Objekt extra eine Delete-Funktion zu implementieren.
Aber im Nachhinein hat sich das als extrem nützlich erwiesen, vor allem in Kombination mit einem eigenen Speichermanager.

Jetzt habe ich mal eine Verständnisfrage zum folgenden Quellcode:

Code: Alles auswählen

class InterfaceBase
{
public:
	virtual void AddRef(void) = 0;
	virtual void Release(void) = 0;
};

class ClassBase : public InterfaceBase
{
public:
	ClassBase(void)
	{
		m_RefCount = 0;
	}

	void AddRef(void)
	{
		++m_RefCount;
	}

	void Release(void)
	{
		--m_RefCount;

		if (0 == m_RefCount)
		{
			Delete();
		}
	}

private:
	virtual void Delete(void) = 0;

	int m_RefCount;
};

class ClassTest : public ClassBase
{
public:
private:
	void Delete(void)
	{
		delete this;
	}
};

ClassTest* pTest = new ClassTest();
// hier wird unnötigerweise der vtable genutzt, warum?
pTest->AddRef();
Warum wird trotzdem beim Aufruf von AddRef/Release der vtable genutzt? Der Compiler sollte doch wissen, dass hier nur die Funktionen von ClassBase aufgerufen werden können.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Krishty »

Goderion hat geschrieben:

Code: Alles auswählen

ClassTest* pTest = new ClassTest();
// hier wird unnötigerweise der vtable genutzt, warum?
pTest->AddRef();
Warum wird trotzdem beim Aufruf von AddRef/Release der vtable genutzt? Der Compiler sollte doch wissen, dass hier nur die Funktionen von ClassBase aufgerufen werden können.
Hast du globale Optimierungen (LTCG) aktiviert, so dass das Konstruktorverhalten bekannt ist? Visual C++ kapituliert oft vor Zeigern; wenn du es als lokale Variable erzeugst, funktioniert es aber manchmal.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Schrompf »

Goderion hat geschrieben:Jedes Objekt kümmert sich dann um die eigene Löschung, was einen virtuellen Destruktor überflüssig macht.
Am Anfang hat mich das etwas genervt, für jedes Objekt extra eine Delete-Funktion zu implementieren.
Aber im Nachhinein hat sich das als extrem nützlich erwiesen, vor allem in Kombination mit einem eigenen Speichermanager.
Aua aua aua. Sorry, aber das ist so falsch, dass ich gar nicht weiß, wie ich das erklären soll. Du erfindest also den Destruktor neu, nur halt manuell. Du hast keine Garantien für nix, keinen Compiler-Support, aber nen eigenen Heap, den Du mit nem Custom Allocator auch kriegen könntest. Und hast Du Deinen eigenen Heap denn jemals profiled? Weißt Du überhaupt, ob er tatsächlich schneller ist? Immerhin trittst Du gegen eines der am besten getesteten und optimierten Code-Teile der CRT an.

Nuja, mach, wie Du magst. Aber das Ganze riecht doch heftig nach Not Invented Here.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Goderion »

Krishty hat geschrieben:Hast du globale Optimierungen (LTCG) aktiviert, so dass das Konstruktorverhalten bekannt ist? Visual C++ kapituliert oft vor Zeigern; wenn du es als lokale Variable erzeugst, funktioniert es aber manchmal.
Vielen Dank für den Tip, habe LTCG mal aktiviert. Es läuft jetzt etwas schneller, aber der vtable wird immer noch genutzt, NARF!
Schrompf hat geschrieben:Aua aua aua. Sorry, aber das ist so falsch, dass ich gar nicht weiß, wie ich das erklären soll. Du erfindest also den Destruktor neu, nur halt manuell.
Ich versehe dich nicht so richtig, glaub ich. Wieso erfinde ich den Destruktor neu? Die Objekte haben weiterhin ihre Destruktoren, die auch aufgerufen werden.
Schrompf hat geschrieben:Du hast keine Garantien für nix, keinen Compiler-Support, aber nen eigenen Heap, den Du mit nem Custom Allocator auch kriegen könntest.
Was für Garantien? Was für Compiler-Support?
Ich bin mir nicht sicher, ob ein Custom Allocator meinen Speichermanager ersetzen kann, bzw. es sind mehrere Speichermanager.
Schrompf hat geschrieben:Und hast Du Deinen eigenen Heap denn jemals profiled? Weißt Du überhaupt, ob er tatsächlich schneller ist? Immerhin trittst Du gegen eines der am besten getesteten und optimierten Code-Teile der CRT an.
Ich habe das mal mit 1.000.000 Objekten getestet (new und delete).
Mit Speichermanager:
9947 µs
9819 µs
9917 µs
9930 µs
Ohne:
65394 µs
65708 µs
65360 µs
65385 µs

Es geht auch nicht nur um die Performance, durch den Speichermanager kann ich immer gut sehen, was im Speicher gerade los ist. Beim Testen finde ich das extrem hilfreich.
Schrompf hat geschrieben:Nuja, mach, wie Du magst. Aber das Ganze riecht doch heftig nach Not Invented Here.
Wonach es auch immer riechen mag, es läuft extrem stabil und sicher. ;-)

EDIT:
Ich habe jetzt auch mal die Performance vom shared_ptr mit meiner Lösung verglichen.
Test zum shared_ptr:

Code: Alles auswählen

	for (int i = 0; i < 1000; ++i)
	{
		std::shared_ptr<ClassTest> P[1000];

		for (int j = 0; j < 1000; ++j)
		{
			P[j] = std::make_shared<ClassTest>();
		}

		for (int x = 0; x < 100; ++x)
		{
			std::shared_ptr<ClassTest> PP[1000];

			for (int j = 0; j < 1000; ++j)
			{
				PP[j] = P[j];
			}
		}
	}
Ergebnis:
1949918 µs
1942421 µs
1940712 µs
1940622 µs

Test zum OLE-Style:

Code: Alles auswählen

	for (UInt32 i = 0; i < 1000; ++i)
	{
		spObject P[1000];

		for (UInt32 j = 0; j < 1000; ++j)
		{
			P[j] = new ClassTest();
		}

		for (UInt32 x = 0; x < 100; ++x)
		{
			spObject PP[1000];

			for (UInt32 j = 0; j < 1000; ++j)
			{
				PP[j] = P[j];
			}
		}
	}
Ergebnis:
1015445 µs
1017010 µs
1018666 µs
1015513 µs

Der shared_ptr ist zu meiner Überraschung langsamer, liegt vielleicht daran, dass der shared_ptr auch einen "Weak-Counter" hat.
Gene
Beiträge: 25
Registriert: 22.05.2003, 11:26

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Gene »

Goderion hat geschrieben: Der shared_ptr ist zu meiner Überraschung langsamer, liegt vielleicht daran, dass der shared_ptr auch einen "Weak-Counter" hat.
Ich tippe auf thread sicherheit.Der std::shared_ptr ist thread sicher, das heißt mehrere threads können "zeitgleich" kopien erstellen und der reference count wird korrekt angepasst. Deine Lösung ist nicht Thread sicher.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Schrompf »

Die selbe Vermutung habe ich auch beim Heap. Gegenüber dem generischen Heap kann man eigentlich nur mit Kontextwissen bei speziellen Anwendungsfällen was rausholen. Dazu sind Custom Allocators ja da, nöch :-)

Aber nuja, jeder wie er mag.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Krishty »

Naja; der generische Heap wird auch durch andere Sachen ausgebremst – Prüfdaten gegen Heap Overflow Attacks und so. Stimmt aber schon; HeapAlloc() sollte meist unter 100 Takten liegen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Goderion »

Gene hat geschrieben:
Goderion hat geschrieben: Der shared_ptr ist zu meiner Überraschung langsamer, liegt vielleicht daran, dass der shared_ptr auch einen "Weak-Counter" hat.
Ich tippe auf thread sicherheit.Der std::shared_ptr ist thread sicher, das heißt mehrere threads können "zeitgleich" kopien erstellen und der reference count wird korrekt angepasst. Deine Lösung ist nicht Thread sicher.
Jo, das scheint es gewesen zu sein. Benutze ich die thread sichere Version, ist der shared_ptr etwas schneller, was wohl an den fehlenden virtual calls liegt.
2206904 µs
2206011 µs
2202900 µs
2205281 µs

Bei mir laufen 5 Threads (Main, Input, Video, Sound und Logik), aber ich brauche an nur sehr wenigen Stellen thread sichere Objekte.
Schrompf hat geschrieben:Die selbe Vermutung habe ich auch beim Heap. Gegenüber dem generischen Heap kann man eigentlich nur mit Kontextwissen bei speziellen Anwendungsfällen was rausholen. Dazu sind Custom Allocators ja da, nöch :-)
Mein Speichermanager ist nicht thread sicher, was natürlich einer der Gründe sein wird, warum ich damit schneller Objekte/Speicher reservieren und wieder freigeben kann. An den Stellen, wo ich den Speichermanager nutze, ist auch keine thread-Sicherheit nötig.
Schrompf hat geschrieben:Aber nuja, jeder wie er mag.
Immer so ein Abschlusssatz... ;-)
Nur weil ich nicht immer den aktuellen Standard nutze, bedeutet das nicht, dass es gleich Kagge ist. Die Unreal Engine nutzt z.B. auch keine std::shared_ptr.
Ich will jetzt die Unreal-Engine nicht mit meinem Programm vergleichen oder das Können der Unreal-Entwickler mit meinen Fähigkeiten, das wäre absurd,
aber nur weil etwas nicht dem Standard entspricht, es gleich zu verteufeln, halte ich für falsch.

Ich überlege auch schon seit gestern, ob und wo ich den std::shared_ptr in meinem Programm sinnvoll einsetzen kann, habe auch schon etwas mit dem shared_ptr experimentiert,
aber aktuell sehe ich ihn ihm keinen Nutzen für mich, vielleicht unter anderen Bedingungen in einem anderen Programm.

Ich bedanke mich für die Antworten und Hinweise, die ihr mir gegeben habt und werde weiter darüber nachdenken.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Objekttyp in C++ zur Laufzeit ermitteln

Beitrag von Schrompf »

Goderion hat geschrieben:Ich bedanke mich für die Antworten und Hinweise, die ihr mir gegeben habt und werde weiter darüber nachdenken.
Nur zur Klarstellung, damit das nicht falsch stehen bleibt: Ich finde es Mist, was Du da baust. Aber das ist nur meine persönliche Meinung. Wenn Du Dir Deiner Sache sicher bist, dann muss Dich das nicht stören. Du kannst in Deinem eigenen Code machen, was Du willst. Mehr sollen meine Nachsätze nicht sagen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Antworten