12 Stimmen

C++ Beispiel für Coding Horror oder brillante Idee?

Bei einem früheren Arbeitgeber schrieben wir Binärnachrichten, die "über den Draht" an andere Computer geschickt werden mussten. Jede Nachricht hatte einen Standard-Header, etwa so:

class Header
{
    int type;
    int payloadLength;
};

Alle Daten waren zusammenhängend (Kopfzeile, unmittelbar gefolgt von den Daten). Wir wollten an die Nutzdaten gelangen, da wir einen Zeiger auf einen Header hatten. Traditionell könnte man etwas sagen wie:

char* Header::GetPayload()
{
    return ((char*) &payloadLength) + sizeof(payloadLength);
}

oder sogar:

char* Header::GetPayload()
{
    return ((char*) this) + sizeof(Header);
}

Das schien mir etwas zu langatmig, also habe ich mir etwas ausgedacht:

char* Header::GetPayload()
{
    return (char*) &this[1];
}

Auf den ersten Blick wirkt es eher beunruhigend, vielleicht zu seltsam für den Gebrauch - aber sehr kompakt. Es gab eine Menge Debatten darüber, ob es brillant oder eine Abscheulichkeit ist.

Also, was ist es - Verbrechen gegen die Codierung oder eine gute Lösung? Hatten Sie jemals einen ähnlichen Kompromiss?

-Update:

Wir haben das Array mit der Größe Null ausprobiert, aber damals gaben die Compiler Warnungen aus. Wir sind schließlich zur inhärenten Technik übergegangen: Message leitet sich von Header ab. Das funktioniert in der Praxis gut, aber im Prinzip sagen Sie, dass eine Nachricht ein Header ist - was ein wenig umständlich ist.

1voto

Steve Jessop Punkte 264569

Ich denke, in der heutigen Zeit, in C++, disqualifiziert der C-Style-Cast zu char* Sie von allen "brillanten Design-Ideen" Preisen, ohne viel Gehör zu finden.

Ich könnte mich dafür entscheiden:

#include <stdint.h>
#include <arpa/inet.h>

class Header {
private:
    uint32_t type;
    uint32_t payloadlength;
public:
    uint32_t getType() { return ntohl(type); }
    uint32_t getPayloadLength() { return ntohl(payloadlength); }
};

class Message {
private:
    Header head;
    char payload[1]; /* or maybe std::vector<char>: see below */
public:
    uint32_t getType() { return head.getType(); }
    uint32_t getPayloadLength() { return head.getPayloadLength(); }
    const char *getPayload() { return payload; }
};

Dies setzt natürlich C99-ish POSIX voraus: Um auf Nicht-POSIX-Plattformen zu portieren, müssen Sie eine oder beide uint32_t und ntohl selbst definieren, und zwar in Bezug auf das, was die Plattform bietet. Das ist normalerweise nicht schwer.

Theoretisch könnten Sie Layout-Pragmas in beiden Klassen benötigen. In der Praxis würde mich das angesichts der tatsächlichen Felder in diesem Fall überraschen. Das Problem kann vermieden werden, indem man die Daten von/nach iostreams Feld für Feld liest/schreibt, anstatt zu versuchen, die Bytes der Nachricht im Speicher zu konstruieren und sie dann in einem Rutsch zu schreiben. Es bedeutet auch, dass man die Nutzlast mit etwas Hilfreicherem als einem char[] darstellen kann, was wiederum bedeutet, dass man keine maximale Nachrichtengröße haben muss oder mit malloc und/oder placement new oder was auch immer herumspielen muss. Natürlich führt es ein bisschen Overhead ein.

0voto

Anthony Giorgio Punkte 1781

Vielleicht hätten Sie eine ausführliche Methode verwenden sollen, die aber durch ein #define-Makro ersetzt wurde? Auf diese Weise können Sie beim Tippen Ihre Kurzschrift verwenden, aber jeder, der den Code debuggen muss, kann ohne Probleme folgen.

0voto

Ich stimme für &this[1]. Ich habe gesehen, dass es ziemlich oft verwendet wird, wenn Dateien geparst werden, die in den Speicher geladen wurden (was auch empfangene Pakete einschließen kann). Es mag auf den ersten Blick etwas seltsam aussehen, aber ich denke, dass die Bedeutung sofort klar sein sollte: Es ist eindeutig die Adresse des Speichers hinter diesem Objekt. Es ist schön, weil es schwer ist, sich zu irren.

0voto

Mike Dunlavey Punkte 39339

Ich verwende nicht gerne Worte wie "Verbrechen". Ich würde eher darauf hinweisen, dass &this[1] Annahmen über das Speicherlayout zu treffen scheint, denen ein Compiler möglicherweise nicht zustimmt. Zum Beispiel könnte jeder Compiler aus seinen eigenen Gründen (z. B. Ausrichtung) Dummy-Bytes an beliebiger Stelle in eine Struktur einfügen. Ich würde eine Technik bevorzugen, die eine größere Garantie bietet, den richtigen Offset zu erhalten, wenn Compiler oder Optionen geändert werden.

0voto

Zusätzlich zu den oben genannten Punkten würde ich sagen, dass dies ein Verbrechen gegen die Interoperabilität und die Grundsätze eines guten Kabelprotokolls ist. Es ist wirklich überraschend, wie viele Programmierer nicht in der Lage/willens sind, eine klare Unterscheidung zwischen der Definition eines Drahtprotokolls und seiner Implementierung zu treffen. Wenn Ihr Protokoll mehr als zwei Tage überleben muss, muss es höchstwahrscheinlich auch mehr als zwei Jahre/OS/Compiler/Sprachen/Diagnosen überleben, und irgendwann wird es kaputtgehen, eher früher als später. Also, machen Sie anderen das Leben leichter, schreiben Sie die Spezifikation des Drahtprotokolls auf und schreiben Sie ordentliche (De-)Serialisierungsroutinen. Andernfalls werden die Leute Ihren Namen immer wieder in nicht so angenehmen Zusammenhängen erwähnen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X