2016-03-30 5 views
0

Ich habe eine Klasse, die mehrere Arrays enthält, deren Größen durch Parameter zu seinem Konstruktor bestimmt werden können. Mein Problem ist, dass Instanzen dieser Klasse Größen haben, die nicht zur Kompilierzeit bestimmt werden können, und ich weiß nicht, wie man eine neue Methode zur Laufzeit weiß, wie groß ich mein Objekt sein muss. Jedes Objekt hat eine feste Größe, aber verschiedene Instanzen können unterschiedliche Größen haben.Verwenden Sie Konstruktor, um Speicher zuzuweisen

Es gibt mehrere Möglichkeiten, um das Problem:
- verwenden, um eine Fabrik
- verwenden, um eine Platzierung Konstruktor
- zuteilen Arrays in den Konstruktor und speichern Zeiger auf sie in meinem Objekt.

Ich bin ein alter Legacy-Code aus einer alten Anwendung in C geschrieben. Im ursprünglichen Code ermittelt das Programm, wie viel Speicher für das gesamte Objekt benötigt wird, ruft malloc() für diesen Betrag auf und initialisiert die verschiedenen Felder.

Für die C++ - Version möchte ich in der Lage sein, einen (ziemlich) normalen Konstruktor für mein Objekt zu erstellen. Es wird ein Nachkomme einer Elternklasse sein, und ein Teil des Codes wird von Polymorphie abhängen, um die richtige Methode aufzurufen. Andere Klassen, die von demselben Elternteil abstammen, weisen zur Kompilierungszeit bekannte Größen auf und stellen somit kein Problem dar.

Ich möchte einige der besonderen Überlegungen vermeiden, die notwendig sind, wenn Platzierung neu verwendet wird, und ich möchte in der Lage sein, die Objekte auf eine normale Weise zu löschen.

Ich möchte vermeiden, im Körper meines Objekts Zeiger zu tragen, teilweise um Probleme mit dem Kopieren des Objekts zu vermeiden, und teilweise, weil ich so viel wie möglich des vorhandenen C-Codes wiederverwenden möchte. Wenn die Eigentumsrechte das einzige Problem wären, könnte ich wahrscheinlich nur gemeinsame Zeiger verwenden und mich nicht sorgen.

Hier ist eine sehr abgespeckte Version des C-Code, der die Objekte erstellt:

typedef struct 
{ 
    int controls; 
    int coords; 
} myobject; 
myobject* create_obj(int controls, int coords) 
{ 
    size_t size = sizeof(myobject) + (controls + coords*2) * sizeof(double); 
    char* mem = malloc(size); 
    myobject* p = (myobject *) mem; 
    p->controls = controls; 
    p->coords = coords; 
    return p; 
} 

Die Arrays innerhalb des Objekts eine feste Größe des Lebens des Objekts beizubehalten. Im obigen Code wird der Speicher nach der Struktur myobject verwendet, um die Array-Elemente zu halten.

Ich fühle mich, als ob ich etwas offensichtlich fehlen könnte. Gibt es eine Möglichkeit, dass ich nicht weiß, einen (ziemlich) normalen Konstruktor in C++ zu schreiben, aber in der Lage zu sagen, wie viel Speicher das Objekt zur Laufzeit benötigt, ohne auf ein "Placement-Neu" -Szenario zurückzugreifen?

+5

Ich verstehe Ihre Anforderung nicht, benötigen Sie speziell das Datenfeld in einem zusammenhängenden Block nach dem Speicher für das Objekt zugeordnet zugeordnet? Ansonsten gibt es keinen Grund, die Verwendung von 'std :: unique_ptr ' oder 'std :: vector ' zu vermeiden. – Jack

+0

Anstatt Arrays variabler Größe oder dynamische Zuweisung zu verwenden, können Sie in Ihrer Klasse einen ['std :: vector'] (http://en.cppreference.com/w/cpp/container/vector) verwenden. Die Größe des Vektors ändert sich nie, so dass sich die Größe Ihrer Klasse niemals ändert. – NathanOliver

+0

Warum konzentrieren Sie sich auf Placement neu? Gewöhnliche neue werden das Problem lösen, oder was? –

Antwort

1

Während ich den Wunsch verstehen, die Änderungen an den vorhandenen C-Code zu begrenzen, wäre es besser, es nicht richtig jetzt tun zu kämpfen als mit Fehlern in der Zukunft. Ich schlage die folgende Struktur und Änderungen an Ihrem Code vor, um damit umzugehen (was ich vermute, würde meistens Code herausziehen, der Offsets berechnet).

+1

Und jetzt ist plötzlich die Anforderung, alten C-Code an Ort und Stelle zu halten, nicht mehr wichtig? Passen Sie sich an. – SergeyA

+0

@SergeyA Ich habe sicherlich nicht erwartet, das "akzeptieren" zu bekommen. Ich wollte nur eine Antwort auf den weiteren Kontext geben und nicht nur auf die unmittelbare technische Frage. –

-1

Wenn die Kommentare endlich etwas Licht auf die tatsächlichen Anforderungen scheded würde die Lösung folgende:

  • einen Puffer zuweisen groß genug, um Ihr Objekt und das Feld am Anfang des
  • neue Verwendung Platzierung zu halten der Puffer

Hier ist, wie:

class myobject { 
    myobject(int controls, int coords) : controls(controls), coords(coords) {} 
    ~myobject() {}; 
public: 
    const int controls; 
    const int coords; 
    static myobject* create(int controls, int coords) { 
     std::unique_ptr<char> buffer = new char[sizeof(myobject) + (controls + coords*2) * sizeof(double)]; 
     myobject obj* = new (buffer.get()) myobject(controls, coords); 
     buffer.release(); 

     return obj; 
    } 
    void dispose() { 
     ~myobject(); 
     char* p = (char*)this; 
     delete[] p; 
    } 
}; 

myobject *p = myobject::create(...); 
... 
p->dispose(); 

(oder in geeigneter Weise innerhalb deleter für Smart-Pointer verpackt)

+1

'myobject * = new ...' ist kein gültiger Code. Sie müssen stattdessen etwas mehr verwenden: 'myobject * obj = new ...; ... return obj; 'Auch Ihre Antwort behandelt nicht die Frage, wie der zugewiesene Speicher freigegeben wird, wenn Sie ihn verwenden, insbesondere in Bezug auf' placement-new'. –

+0

@SergeyA Das sieht nach einem Fabrikansatz aus. Mein Hauptanliegen hier ist, wird '~ myobject()' richtig aufgerufen, wenn ich anschließend 'delete' auf einen Zeiger auf die Elternklasse des Objekts verwende? – Logicrat

+0

@Logicrat, ich sehe keine Eltern hier. Im übergeordneten Szenario müssen Sie Destruktor virtuell machen. – SergeyA

-1

Neue Antwort (gegeben Kommentar von OP):

ein std::vector<byte> der richtigen Größe zuweisen. Das Array, das dem Vektor zugeordnet ist, ist zusammenhängender Speicher. Diese Vektorgröße kann berechnet werden und der Vektor wird Ihren Speicher korrekt verwalten. Sie müssen immer noch sehr vorsichtig sein, wie Sie Ihren Zugriff auf dieses Byte-Array offensichtlich verwalten, aber Sie können zumindest Iteratoren und dergleichen verwenden (wenn Sie möchten).

Durch die Art und Weise hier ist ein wenig Vorlage, was ich entlang Byte Blobs mit etwas mehr Gnade (Note bewegen verwenden, um dieses Aliasing-Probleme hat, wie unten durch Sergey in den Kommentaren darauf hingewiesen, ich es hier verlasse, weil es scheint ein gutes Beispiel dafür, was nicht zu tun ... :-)):

template<typename T> 
T readFromBuf(byte*& ptr) { 
    T * const p = reinterpret_cast<T*>(ptr); 
    ptr += sizeof(T); 
    return *p; 
} 

Alte Antwort:

Da die Kommentare vorschlagen, können Sie leicht ein std::vector verwenden, was zu tun Sie wollen. Auch ich möchte einen anderen Vorschlag machen.

Die obige Codezeile deutet darauf hin, dass Sie eine "versteckte Struktur" in Ihrem Code haben. Ihr myobject struct hat zwei int Werte, aus denen Sie die Größe dessen berechnen, was Sie tatsächlich benötigen. Was Sie wirklich brauchen, ist dies:

struct ControlCoord { 
    double control; 
    std::pair<double, double> coordinate; 
}; 
std::vector<ControlCoord>> controlCoords; 
+0

Verletzung der strengen Aliasing-Regel sollte von einem Monat nach der Lesung D. Knuth bestraft werden. – SergeyA

+0

@SergeyA Danke für den Kommentar. Wie gehe ich hier auf Speicher-Aliasing-Probleme ein? Ich dachte, dass der Vektor ein zusammenhängendes Backing-Array vorschreibt? Wäre das nicht genau das gleiche wie wenn man es über 'new []' alloziert? – Dennis

+0

Hier: 'T * const p = reinterpret_cast (ptr);' Nun, nicht genau hier, aber später, wenn 'p 'verwendet wird. – SergeyA

2

Wie wärs mit einem pragmatischen Ansatz: die Struktur halten, wie (wenn die Kompatibilität mit C wichtig ist) und wickeln Sie es in eine C++ Klasse?

typedef struct 
{ 
    int controls; 
    int coords; 
} myobject; 

myobject* create_obj(int controls, int coords); 
void dispose_obj(myobject* obj); 


class MyObject 
{ 
public: 
    MyObject(int controls, int coords) {_data = create_obj(controls, coords);} 
    ~MyObject() {dispose_obj(_data);} 

    const myobject* data() const 
    { 
     return _data; 
    } 

    myobject* data() 
    { 
     return _data; 
    } 

    int controls() const {return _data->controls;} 
    int coords() const {return _data->coords;} 
    double* array() { return (double*)(_data+1); } 

private: 
    myobject* _data; 
} 
+0

Wenn Sie diese Route gehen, würde ich vorschlagen, auch Methoden offen zu legen, um auf die 'doppelten' Daten zuzugreifen, zB:' double * array() {return (double *) (_ data + 1); } ' –

+0

@RemyLebeau: einverstanden, danke für den Tipp. – alexm

1

Um die Semantik des ursprünglichen Codes zu erhalten, in dem die Struktur und Anordnung in einem einzigen contigious Speicherblock sind, können Sie einfach malloc(size) mit new char[size] statt ersetzen:

myobject* create_obj(int controls, int coords) 
{ 
    size_t size = sizeof(myobject) + (controls + coords*2) * sizeof(double); 
    char* mem = new char[size]; 
    myobject* p = new(mem) myobject; 
    p->controls = controls; 
    p->coords = coords; 
    return p; 
} 

Sie müssen verwenden, um eine Art Guss, wenn der Speicher mit delete[] zu befreien, aber:

myobject *p = create_obj(...); 
... 
p->~myobject(); 
delete[] (char*) p; 

In diesem Fall würde ich vorschlagen, wrapp ing diese Logik in eine andere Funktion:

void free_obj(myobject *p) 
{ 
    p->~myobject(); 
    delete[] (char*) p; 
} 

myobject *p = create_obj(...); 
... 
free_obj(p); 

aber sagen, dass, wenn Sie erlaubt sind, wäre es besser, den Code neu schreiben C++ Semantik statt, zum Beispiel zu folgen:

struct myobject 
{ 
    int controls; 
    int coords; 
    std::vector<double> values; 

    myobject(int acontrols, int acoords) : 
     controls(acontrols), 
     coords(acoords), 
     values(acontrols + acoords*2) 
    { 
    } 
}; 

Und dann können Sie dies tun:

std::unique_ptr<myobject> p = std::make_unique<myobject>(...); // C++14 
... 

std::unique_ptr<myobject> p(new myobject(...)); // C++11 
... 

std::auto_ptr<myobject> p(new myobject(...)); // pre C++11 
... 
+0

Hat dieser Ansatz (bei der Verwendung der RAW-Umwandlung) nicht auch das Problem des möglichen Aliasing, das Sergey bezüglich meiner Verwendung von 'reininterpret_cast' erwähnt? – Dennis

+0

Ich glaube, es ist eine Verletzung der strengen Aliasing-Regel. 'myobject * p = (meinObjekt *) mem;' – SergeyA

+0

@SergeyA: daher die Alternative 'placement-new'. –

Verwandte Themen