Seite 1 von 1

(gelöst) [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 08:31
von Krishty
Hi,

Ich habe zwei Klassen. Die erste Klasse wird bei jeder Konstruktion definitiv eine Instanz der zweiten Klasse allokieren. Ich möchte nun, dass beide Objekte in nur einem Speicherblock mit einer Allokation untergebracht werden können.

Normalerweise würde ich die zweite Klasse einfach zu einem Member der ersten Klasse machen, aber es handelt sich um ein Interface … alle Implementationen des Interfaces müssten identischer Größe sein, weil sie nur pur virtuelle Funktionen implementieren und nichts Neues (weder Variablen noch Funktionen) hinzufügen.

In VC6 konnte man afaik den operator sizeof überladen, so dass man sizeof(Klasse1) einfach um sizeof(Klasse2) Bytes größer gemacht und das zusätzliche Objekt per (this + 1) erhalten hat … afaik ist das Überladen von sizeof aber nicht mehr möglich …

… kann ich den operator new der Klasse so überladen, dass er sizeof(Klasse2) (plus Alignment) Bytes mehr allokiert?

Gruß, Ky

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 08:39
von TGGC
Sowas in der Art willst du aber nicht?

Code: Alles auswählen

BYTE* Speicher= new BYTE[ Bla1 + Bla2];
Klasse2 *p1 = new (Speicher) Klasse1();
Klasse2 *p2 = new (Speicher + Bla1) Klasse2();
Nur so als Idee... f'`8k

[ ] Autocogito


Gruß, TGGC (Was Gamestar sagt...)

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 10:00
von knivil
Sowas in der Art willst du aber nicht?
Sieht so aus. Die Frage bleibt: Warum?

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 10:08
von Krishty
So in etwa, nur dass das erste Objekt allein den Speicher des zweiten Objekts verwalten soll (ohne dass es nach außen sichtbar ist) ... und Stack-tauglich sollte es auch sein ... mit sizeof ging das damals, aber jetzt ... :(
knivil hat geschrieben:Sieht so aus. Die Frage bleibt: Warum?
Ist mir ein Dorn im Auge, wenn ein Objekt quasi nur ein Zeiger ist und zusätzlich Speicher allokiert, wenn doch von vornherein feststeht, wie groß die Daten mal sein werden - bloß um der virtuellen Funktionen Willen ... außerdem mag ich die Sachen auf dem Stack lieber als auf dem Heap.

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 10:30
von TGGC
Wo der Speicher herkommt, ist damit ja auch nicht festgelegt, "Speicher" koennte genauso auf ein Stueck Speicher auf dem Stack zeigen. Ansonsten sorge halt einfach dafuer das deine Klasse1 um n Bytes zu gross ist, indem du entsprechende Member hinzufuegst - da dein n ja ehh konstant sein soll. f'`8k

[ ] Autocogito


Gruß, TGGC (Was Gamestar sagt...)

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 10:50
von Schrompf
Das wäre auch mein Vorschlag: verschaffe Dir zusätzlichen Platz in Klasse1, indem Du z.B. ein char[20] mit reinnimmst. Dann benutze Placement New auf diesem Zeiger, wie TGGC das vorgeschlagen hat.

Wirst Du denn wirklich viele hunderttausend Instanzen der Klasse anlegen, damit sich das Manöver lohnt? Dadurch verbaust Du Dir nämlich auch die Möglichkeit, eine Instanz der zweiten Klasse nachträglich reinzureichen. Du bist damit quasi gezwungen, die zweite Klasse über eine Factory im Konstruktor der ersten Klasse anzulegen.

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 13:10
von Aramis
In VC6 konnte man afaik den operator sizeof überladen, so dass man sizeof(Klasse1) einfach um sizeof(Klasse2) Bytes größer gemacht und das zusätzliche Objekt per (this + 1) erhalten hat … afaik ist das Überladen von sizeof aber nicht mehr möglich …
Exakt, und zwar weil sizeof *zwingend* eine Konstante sein muss. Unter anderem, weil es sonst nicht in Templates benützt werden könnte. Mit constexpr im neuen Sprachstandard wäre es zwar theoretisch möglich den Operator überladbar zu machen, glücklicherweise scheint es aber niemand vorzuhaben.
Normalerweise würde ich die zweite Klasse einfach zu einem Member der ersten Klasse machen, aber es handelt sich um ein Interface … alle Implementationen des Interfaces müssten identischer Größe sein, weil sie nur pur virtuelle Funktionen implementieren und nichts Neues (weder Variablen noch Funktionen) hinzufügen.
Don't do it, it's evil. Soweit ich weiß, sagt der Standard nichts über die tatsächliche Implementierung virtueller Funktionen aus.
Ist mir ein Dorn im Auge, wenn ein Objekt quasi nur ein Zeiger ist und zusätzlich Speicher allokiert, wenn doch von vornherein feststeht, wie groß die Daten mal sein werden - bloß um der virtuellen Funktionen Willen
Dann solltest du nicht am Objekt-Design ansetzen, sondern am Speichermanager. Beispielsweise könntest du für stark frequentierte Klassen, wie diese, einfach einen separaten Heap einsetzen, den du manuell verwaltest und deinen Bedürfnissen anpasst. Ich bezweifle aber dass es etwas bringen wird. Bei Assimp haben wir etwas ähnliches probiert (aber schlussendlich dann doch gelassen) um die vielen Mini-allocs für die einzelnen Polygone zu beschleunigen.

Alex

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 13:38
von Jörg
Wie dynamisch muss die Loesung sein? Ist die Beziehung zwischen Klasse und Interface-Implementierung fest fuer die Lebenszeit? Wer garantiert, dass deine Interfaces wirklich alle immer in das gleiche Schema (Aufbau) passen....neben der vtable wird doch irgendwann noch etwas dazu kommen an Daten, oder? Was spricht gegen eine feste Beziehung, die sich ueber Vererbung mit Template-Unterstuetzung leicht erledigen laesst ? Fuer den Fall, dass das Interface (seine Implementierung meine ich) mal ausgetauscht werden muss, tut es eventuell ein angepasster copy-constructor auch (je nach Haeufigkeit).

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 13:44
von Aramis
Das folgende kompiliert zumindest mal unter MSVC. Ob es wirklich standardkonform ist - eher nicht. Aber vielleicht ist es ein Ansatz, was nicht heißt, dass ich das ganze befürworte ;-)

Code: Alles auswählen


// --------------------------------------------------------------------------------------------
template <typename T>
struct OnStack {
	enum {extra_size=0};
	static OnStack Get () {return OnStack();}
};

template <typename T>
void* operator new(size_t s, const OnStack<T>&) { 
	return alloca(s+ OnStack<T> :: extra_size ); // afaik manchmal auch als _alloca, vor allem (afaik) nicht-standard!
}

template <typename T>
struct OnHeap {
	enum {extra_size=0};
};

template <typename T>
void* operator new(size_t s, const OnHeap<T>&) { 
	return malloc(s+ OnHeap<T> :: extra_size );
}

// --------------------------------------------------------------------------------------------
#define IMPLEMENT_OVERRIDE_SIZEOF(cl) \
template <> \
struct OnStack<foobar> { \
	enum {extra_size=cl :: extra_size}; \
	static OnStack Get () {return OnStack();} \
}; \
template <> \
struct OnHeap<foobar> { \
	enum {extra_size=cl :: extra_size}; \
	static OnHeap Get () {return OnHeap();} \
};

#define OVERRIDE_SIZEOF(extra) \
	enum {extra_size=extra};

// --------------------------------------------------------------------------------------------
#define NEW_HEAP(alloctype) \
	new (OnHeap<alloctype> :: Get()) alloctype;

#define NEW_STACK(alloctype) \
	new (OnStack<alloctype> :: Get()) alloctype;

// --------------------------------------------------------------------------------------------
class foobar	{
public:
	OVERRIDE_SIZEOF(10);
};
IMPLEMENT_OVERRIDE_SIZEOF(foobar);

// --------------------------------------------------------------------------------------------
void test
{
	int* p1 = NEW_STACK(int);
	foobar* p2 = NEW_STACK(foobar);
	foobar* p3 = NEW_HEAP(foobar);
}


Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 14:02
von Krishty
@TGGC, Schrompf: Placement-new scheint die optimale Lösung zu sein … bevor ich es aber implementiere, noch eine Frage: Gibt es ein entsprechendes delete (ich habe noch im Kopf "There is no placement delete in C++"), oder muss ich der placement-allokierten Klasse eine Funktion zur Bereinigung mitgeben, wenn ich die Daten wieder zerstören will?
Schrompf hat geschrieben:Wirst Du denn wirklich viele hunderttausend Instanzen der Klasse anlegen, damit sich das Manöver lohnt? Dadurch verbaust Du Dir nämlich auch die Möglichkeit, eine Instanz der zweiten Klasse nachträglich reinzureichen. Du bist damit quasi gezwungen, die zweite Klasse über eine Factory im Konstruktor der ersten Klasse anzulegen.
Nicht hunderttausende, aber tausende schon. Ich muss sowieso den Weg über die Factory gehen, da die Schnittstelle pur virtuelle Funktionen enthält und nach außen unsichtbar ist. Entsprechend ist in der Oberklasse auch schon alles bezüglich RAII und der Erkennung ungültiger Zustände abgeklärt.
Aramis hat geschrieben:Exakt, und zwar weil sizeof *zwingend* eine Konstante sein muss. Unter anderem, weil es sonst nicht in Templates benützt werden könnte.
Nicht, dass du mich falsch verstehst: Auch der Wert des überladenen operator sizeof in VC6 musste eine zur Kompilierzeit bekannte Konstante sein, auch wenn dahinter kein Sprachkonzept wie constexpr stand. Hätte man sizeof laufzeitabhängig machen können, wäre da sicher das ein oder andere Massaker rausgekommen :D
Aramis hat geschrieben:Don't do it, it's evil. Soweit ich weiß, sagt der Standard nichts über die tatsächliche Implementierung virtueller Funktionen aus.
Aber ganz egal, wie (oder ob überhaupt) die virtuellen Funktionen implementiert werden – sizeof dürfte einen da nicht betrügen.
Aramis hat geschrieben:Dann solltest du nicht am Objekt-Design ansetzen, sondern am Speichermanager.
Ich benutze schon den Low-Fragmentation-Heap … es geht auch weniger darum, noch 0.0001% Performance rauszuholen, sondern um Kosmetik und Perfektion. So lang wie du mich kennst, müsstest du wissen, dass ich auch die absurdesten Experimente nicht aufgebe, bis mir alles um die Ohren fliegt ;)

Deinen Code schaue ich mir gleich an, habe nun zu lange an diesem Post getippt.
Jörg hat geschrieben:Wie dynamisch muss die Loesung sein? Ist die Beziehung zwischen Klasse und Interface-Implementierung fest fuer die Lebenszeit? Wer garantiert, dass deine Interfaces wirklich alle immer in das gleiche Schema (Aufbau) passen....neben der vtable wird doch irgendwann noch etwas dazu kommen an Daten, oder? Was spricht gegen eine feste Beziehung, die sich ueber Vererbung mit Template-Unterstuetzung leicht erledigen laesst ? Fuer den Fall, dass das Interface (seine Implementierung meine ich) mal ausgetauscht werden muss, tut es eventuell ein angepasster copy-constructor auch (je nach Haeufigkeit).
Die Beziehung ist nicht fest auf Lebenszeit. Dass alle Implementationen der Schnittstelle nahezu gleich sind, wird dadurch garantiert, dass sie alle nur Instanzierungen desselben Templates mit je anderem enum-Parameter und ohne Spezialisierung (d.h. alle vom Compiler generiert) sind. Desweiteren verwaltet die Klasse das Interface und lässt so nichts „Fremdes“ zu.
Daten kommen auch nicht mehr hinzu. Ich brauche einfach nur andere Ergebnisse in den Funktionen (und auch die sind komplett Compiler-generiert), alle Daten sind bereits in der Basisklasse gespeichert und werden es wohl auch immer sein. Eine feste Beziehung mit Templates hatte ich bis letzten Monat implementiert, deswegen ist der Code aber am Ende exponentiell gewachsen. Für einen Copy-Ctor sind die Daten, die die Implementierungen der Interfaces frei allokiert mitbringen, einfach zu groß und zu sperrig (~4 MiB pro Kopie).


Achja, danke für die rege Teilnahme – sonst dümpeln meine Threads ja tagelang vor sich hin, bevor sich was tut :D

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 14:15
von Schrompf
Krishty hat geschrieben:@TGGC, Schrompf: Placement-new scheint die optimale Lösung zu sein … bevor ich es aber implementiere, noch eine Frage: Gibt es ein entsprechendes delete (ich habe noch im Kopf "There is no placement delete in C++"), oder muss ich der placement-allokierten Klasse eine Funktion zur Bereinigung mitgeben, wenn ich die Daten wieder zerstören will?
Björne schreibt das gleiche: http://www.research.att.com/~bs/bs_faq2 ... ent-delete

Essenz: Es gibt kein "Placement Delete", aber wohl der manuelle Aufruf des Destruktors. Das kommt dem "Placement Delete" so nahe, wie ich mir nur vorstellen kann.

Code: Alles auswählen

Klasse a;
a.~Klasse();
Ich fänd's schön, wenn man auch Konstruktoren auf diese Art manuell aufrufen könnte. Wäre nützlich zur Umleitung in der Implementation von Konstruktoren.
Achja, danke für die rege Teilnahme - sonst dümpeln meine Threads ja tagelang vor sich hin, bevor sich was tut :D
Weil Du was Entsetzliches vorhast, zu dem jeder ne Meinung hat :-)

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 14:27
von Krishty
Schrompf hat geschrieben:Ich fänd's schön, wenn man auch Konstruktoren auf diese Art manuell aufrufen könnte.
Bei VC6 konnte man das, darum habe ich auch mit dem explicit-destructor-call so gehadert – ich dachte, das wäre auch so eine Microsoft-Extension von früher … aber gut, dann baue ich mal einen Prototyp :)
Schrompf hat geschrieben:Weil Du was Entsetzliches vorhast
Als ob das zum ersten Mal so wäre :D

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 14:35
von TGGC
Krishty hat geschrieben:@TGGC, Schrompf: Placement-new scheint die optimale Lösung zu sein … bevor ich es aber implementiere, noch eine Frage: Gibt es ein entsprechendes delete (ich habe noch im Kopf "There is no placement delete in C++"), oder muss ich der placement-allokierten Klasse eine Funktion zur Bereinigung mitgeben, wenn ich die Daten wieder zerstören will?
Nennen wir diese Funktion doch einen "Destruktor". f'`8k

[ ] Autocogito


Gruß, TGGC (Was Gamestar sagt...)

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 15:29
von kimmi
Hast du dir auch schon mal den Small-Object-Allocator der Loki-Lib angesehen? Viellleicht bietet der dir etwas, der deinen Anforderungen entgegenkommt.

Gruß Kimmi

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 15:32
von Krishty
Ja … das Buch liegt eigentlich nie weiter als zwei Meter entfernt … aber die besten Allokationen sind eben die, die man garnicht erst durchführt.

Re: [C++] Zwei Objekte in einem new

Verfasst: 04.06.2009, 16:04
von Krishty
Okay, es scheint zu funktionieren – keine Memory Leaks, keine Zugriffsverletzungen beim Aufruf der virtuellen Funktionen, alles wie gehabt :)

Nochmal für’s Protokoll:
– Ausreichend Speicher in der Containerklasse reservieren.
– Objekt mit Placement-New füllen.
– Objekt mit explizitem Destruktoraufruf wieder freigeben.

Ein Beispiel, der Übersicht halber ohne virtuelle Funktionen:

Code: Alles auswählen

class Container {
    class Subclass { … };

    char Pool[sizeof(Subclass)];

    Container() {
        new (this->Pool) Subclass;
    }

    ~Container() {
        reinterpret_cast<Subclass *>(this->Pool)->~Subclass();
    }
};
Ich danke euch für eure Hilfe!