Stellen Sie sich eine Klasse 'B' vor, die einfache char Mitgliedsvariablen in einer bestimmten Reihenfolge enthält.

class B {
    char x1;
    char x2;
    char x3;
    char x4;
}

Ich habe einen Puffer A, der bereits Daten in derselben Reihenfolge enthält, wie sie in B definiert ist. Ein anderer Prozess hat bereits A mit den Daten geladen.

char A[4];
  1. Ist es möglich, ein Objekt vom Typ B zu erstellen, das die Daten von A enthält, ohne dass der Konstruktor die Daten kopiert? Das heißt, ich möchte ein B -Objekt auf den A -Puffer "überlagern", damit ich B -Methoden für die Daten verwenden kann, ohne den Overhead einer Kopie oder die Zuweisung von zu verursachen Erinnerung.

  2. Angenommen, es gibt eine Lösung für Frage 1, gäbe es einen Grund, warum ich nicht auch eine Klasse D definieren könnte, die von B abgeleitet ist und Methoden enthält, die auf die Mitgliedsvariablen von B verweisen , aber welche selbst enthält keine neuen Mitgliedsvariablen?

4
sifferman 16 Apr. 2018 im 22:22

4 Antworten

Beste Antwort

Da ein A kein B ist, gibt es keine legale Möglichkeit, ein A als B zu behandeln. Das heißt, wenn A eine Standard-Layoutklasse ist, sollten Sie in der Lage sein, sie zu übertragen, und sie wird "funktionieren", aber sie ist nicht legal. Zum Beispiel

struct A
{
    char data[6] = "hi 0/";
    int a = 10;
    int b = 20;
};

struct B
{
    char x1;
    char x2;
    char x3;
    char x4;
    char x5;
    char x6;
};

std::ostream& operator <<(std::ostream& os, B& b)
{
    return os << b.x1 << b.x2 << b.x3 << b.x4 << b.x5 << b.x6;
}

int main()
{
    A a;
    B* b = reinterpret_cast<B*>(&a);
    std::cout << *b;
}

Dies funktioniert, da das Array und die Mitglieder in jeder Klasse denselben Speicherbereich belegen, dies ist jedoch nicht garantiert. Nach x1 in B kann es zu einem Auffüllen kommen, was bedeutet, dass nicht alle Mitglieder dem Array zugeordnet werden.

Wenn Sie nun B überarbeiten, um ein Array anstelle von separaten Mitgliedern wie zu haben

struct A
{
    char data[6] = "hi 0/";
    int a = 10;
    int b = 20;
};

struct B
{
    char data[6];
};

Dann könnten Sie eine Union verwenden, um sowohl A als auch B zu speichern, und da B dieselbe gemeinsame Anfangssequenz als A ist es legal, b zu verwenden. Das könnte so aussehen

union Converter
{
    Converter() : a{} {}
    A a;
    B b;
};

std::ostream& operator <<(std::ostream& os, B& b)
{
    return os << b.data;
}

int main()
{
    Converter c;
    std::cout << c.b;
}

Und jetzt ist die Besetzung weg und wir haben eine Garantie vom Standard dass dies sicher ist

6
NathanOliver 16 Apr. 2018 im 20:19

So unangenehm es auch ist, es gibt keinen legalen (standardmäßigen) Weg, dies zu erreichen. Stattdessen haben Sie eine illegale, aber normalerweise funktionierende (über eine Vielzahl von Orten hinweg verwendete) oder legale, aber optimierungsabhängige.

Unabhängig von der Methode wird davon ausgegangen, dass für B-Mitglieder kein Auffüllen vorhanden ist (fügen Sie [[gnu::packed]] für gcc oder etwas anderes für Ihren Compiler zur B-Definition hinzu, um sicherzustellen, dass kein Auffüllen stattfindet).

Erstens wäre illegal - Sie können den Typ alias. Dies verstößt gegen strenge Aliasing-Regeln, funktioniert jedoch bekanntermaßen auf vielen Plattformen und Compilern. Codebeispiel:

const B* b = reinterpret_cast<const B*>(&a[0]);

Die zweite Möglichkeit besteht darin, sich auf den Optimierer des Compilers zu verlassen. Es ist oft leistungsfähig genug, um zu erkennen, dass die Daten nicht tatsächlich kopiert werden müssen, und verwendet nur Originalwerte. Dies ist nicht immer der Fall. Wenn Sie sich im leistungskritischen Bereich auf diese Technik verlassen, sollten Sie den generierten Code überprüfen und bei jedem Compiler-Upgrade erneut überprüfen.

Dieser Code setzt eine Optimierung voraus:

B b;
memcpy(&b, &a[0], sizeof(b));
// use b in non-modifying way
// Compilers usually will not issue a copy here, YMMV
6
SergeyA 16 Apr. 2018 im 20:07

Ja, du kannst; Wenn Sie eine neue Platzierung verwenden, wie hier gefragt (https://isocpp.org/wiki) / faq / dtors # platzierung-neu), das den Speicherort für B angibt und hier dokumentiert ist (https://isocpp.org/wiki/faq/dtors#placement-new),

Achten Sie jedoch auf das Destruktorverhalten bei der Verwendung, und Initialisierer in B würden die Dinge durch Überschreiben der Daten durcheinander bringen. Sie müssen auch sehr vorsichtig mit der Speicherausrichtung und den diesbezüglichen Anforderungen der Objekte sein.

Spezifische Warnhinweise auf dieser Seite:

You are also solely responsible for destructing the placed object. This is done by explicitly calling the destructor:

ADVICE: Don’t use this “placement new” syntax unless you have to. Use it only when you really care that an object is placed at a particular location in memory. For example, when your hardware has a memory-mapped I/O timer device, and you want to place a Clock object at that memory location.

Und DANGER: You are taking sole responsibility that the pointer you pass to the “placement new” operator points to a region of memory that is big enough and is properly aligned for the object type that you’re creating. Neither the compiler nor the run-time system make any attempt to check whether you did this right. If your Fred class needs to be aligned on a 4 byte boundary but you supplied a location that isn’t properly aligned, you can have a serious disaster on your hands (if you don’t know what “alignment” means, please don’t use the placement new syntax). You have been warned.

-2
Halcyon 16 Apr. 2018 im 19:40

Ja, Sie können einen vorhandenen Puffer verwenden, um ein neues Objekt darin zu erstellen. Sie würden einfach den neuen Operator für die Platzierung verwenden:

class B
{
    char x1;
    char x2;
    char x3;
    char x4;
};

char A[4];


B *b = new(A) B; 

Jetzt verwendet b den Stapelspeicher von A [4]; Der Code geht davon aus, dass B ohne Ausrichtung gepackt ist.

class D : public B
{
public:
   void MyMethod();
};

D *d = new (A) D;

Sie können Code in eine abgeleitete Klasse einfügen und dann denselben Ansatz zum Erstellen von D verwenden.

Der folgende Code:

#include <new>

#include <iostream>

struct B
{
    char x1;
    char x2;
    char x3;
    char x4;
};

char A[4] = { 'M','e','o','w' };

int main()
{
    B *b = new (A) B;

    std::cout << "The cat's " << b->x1 << b->x2 << b->x3 << b->x4 << std::endl;

    return 0;
}

Ausgänge: Der Miau der Katze

-2
Markus Schumann 16 Apr. 2018 im 21:09