2017-05-08 3 views
2

Ich aktualisiere ein System, das Multicast-Nachrichten empfängt, analysiert die Daten in Klassen und übergibt dann einen Basisklassenzeiger an einen separaten Thread über eine Warteschlange. Der andere Thread liest dann die Daten aus den Klassen und speichert sie in Tabellen.Vermeiden Sie statische und dynamische Umwandlung

Es gibt 2 verschiedene Arten von Nachrichten, die wir erhalten. Hier ist die Klasse Design:

class BaseMsg 
{ 
public: 
    BaseMsg(char tp) : msgType(tp) {} 
    virtual ~BaseMsg() = 0; 

    char msgType; 
} 

class MsgType1 : public BaseMsg 
{ 
public: 
    MsgType1(int a, int b) : BaseMsg('1'), val1(a), val2(b) {} 
    virtual ~MsgType1(); 

    int val1, val2; 
} 

class MsgType2 : public BaseMsg 
{ 
public: 
    MsgType2(double a, double b) : BaseMsg('2'), val1(a), val2(b) {} 
    virtual ~MsgType2(); 

    double val1, val2; 
} 

Hier ist der Empfangscode:

void reciveMessage(char *datablob, MsgQueue *queue) 
{ 
    BaseMsg *msgReceived; 
    char type = datablob[0]; // message type is the first character in the data, 
           // the rest of the blob varies based on that type 
    if (type == '1') { 
     // code for parsing type 1 into 2 ints, i1, i2 
     msgReceived = new MsgType1(i1, i2); 
    } else { 
     // code for parsing type 2 into 2 doubles, d1, d2 
     msgReceived = new MsgType2(d1, d2); 
    } 
    queue->push(msgReceived); 
} 

Hier ist der Prozess-Code, der in einem separaten Thread ausgeführt wird:

void processMessage(MsgQueue *queue) 
{ 
    BaseMsg *msgToProcess = queue->pop(); 

    if (msgToProcess->msgType == '1') { 
     MsgType1 *mt1 = static_cast<MsgType1 *>(msgToProcess); 
     // do stuff with the message type 1 data 
    } else { 
     MsgType2 *mt2 = static_cast<MsgType2 *>(msgToProcess); 
     // do stuff with the message type 2 data 
    } 
} 

Ich weiß, dass die Prüfung für den Nachrichtentyp dann downcasting ist schlampig, aber angesichts der Beschränkungen der Kommunikation durch die Warteschlange, konnte ich nicht mit einer besseren Lösung kommen. Selbst mit dynamic_cast <> (die ich aus Leistungsgründen nicht tun möchte) würde das gleiche Problem führen; es muss immer noch wissen, welche Art von Klasse zu konvertieren msgToProcess.

Irgendwelche Vorschläge darüber, wie ich die Überprüfungen loswerden könnte & Casting? Ich habe viel Erfahrung mit C & C++, aber nicht viel mit OO-Design, so kann es einen Weg, über den ich nichts weiß.

Beachten Sie auch, dass dies ein extrem reduziertes Beispiel zur Veranschaulichung der Frage ist. Es sind tatsächlich über 50 verschiedene Nachrichtentypen, und die Leistung ist kritisch, da wir Millionen von Nachrichten pro Sekunde erhalten können.

+1

In der "Do stuff" -Abschnitt, gibt es eine Möglichkeit, die Sie können Imp es in Bezug auf virtuelle Funktionsaufrufe in der Basisklasse? – RyanP

+0

Sie können die Anzahl der Stellen reduzieren, an denen "downcast" verwendet wird, aber Sie können es nicht vermeiden. Siehe [meine Antwort zu einem anderen SO-Post] (http://stackoverflow.com/a/43038856/434551) für Ideen, wie man es weniger repetitiv macht. –

+0

Wenn Sie sich nicht auf RTTI (Casting) verlassen möchten, möchten Sie vielleicht eine diskriminierende Union verwenden, aber Sie würden immer noch eine switch-Anweisung über eine enum benötigen, es gibt auch std :: variant, die einen ähnlichen Job erledigt. Ich habe die Leistung nicht ausprobiert, aber ich nehme an, dass es sehr schnell geht, da Sie keine Umleitung speichern müssen, damit Sie von der Cache-Lokalität profitieren können. – AlexG

Antwort

2

Ich würde eine rein virtuelle Funktion namens doStuff() innerhalb der Klasse BaseMsg definieren.
Jede abgeleitete Klasse implementiert diese Methode.
Innerhalb Ihres processMessage, rufen Sie einfach msgToProcess->doStuff()

+0

Ich dachte über diese Lösung nach, aber das würde bedeuten, dass die Klassen, die jetzt nur Nachrichten enthalten, über die ganze Arbeit, die in den "do stuff" -Abschnitten vor sich geht, Bescheid wissen müssen, was eine Unordnung der Datenmanipulation darstellt. und Umpacken. Das Ziel war es, die zwei Teile minimal zu verbinden, und nur den "Do-stuff" -Code über beide Sätze von Strukturen wissen zu lassen. –

+0

doStuff (BaseProcessor *) kann eine bessere Skalierbarkeit bieten:) – felix

+0

Dies. "Ich weiß, dass die Überprüfung auf Nachrichtentyp und Downcasting schlampig ist, aber ..." Sie sollten sich einfach auf Polymorphie verlassen, dafür ist es da. –

3

ich mit anderen zustimmen, der die inhärenten Eigenschaften von Polymorphismus verwenden vorgeschlagen haben und Member-Funktionen verwenden.

Aber ich verstehe auch die Notwendigkeit, die Nachrichtenklassen ziemlich sauber zu halten. Ich denke, ein Besucher-Muster verwenden (das beruht auf Polymorphismus) eine gute Idee, in diesem Fall sein kann:

class BaseMsg { 
public: 
    virtual void accept(MsgProcessor& p) = 0; 
}; 

class Msg1 : BaseMsg { 
public: 
    void accept(MsgProcessor& p) { p.process(*this); } 
}; 

class Msg2 : BaseMsg { 
public: 
    void accept(MsgProcessor& p) { p.process(*this); } 
}; 

class MsgProcessor { 
public: 
    void process(Msg1& m); 
    void process(Msg2& m); 
} 

Die kühle Sache ist, dass dies nicht kompilieren, wenn eine der Funktionen für jede der fehlenden abgeleitete Klassen.

bearbeiten: Fest Sachen wiesen darauf hin, in den Kommentaren

edit2: mit zwei Codebases & minimale Änderungen in der externen Code-Basis:

Externe Code-Basis:

class MsgVisitor; 
class BaseMsg { 
public: 
    virtual void accept(MsgVisitor&) = 0; 
}; 

class Msg1; 
class Msg2; 
class MsgVisitor { 
public: 
    virtual void visit(Msg1&) = 0; 
    virtual void visit(Msg2&) = 0; 
}; 

class Msg1 : public BaseMsg { 
public: 
    void accept(MsgVisitor& v) { v.visit(*this); } 
}; 

class Msg2 : public BaseMsg { 
public: 
    void accept(MsgVisitor& v) { v.visit(*this); } 
}; 

Local Code-Basis:

class MsgProcessor : public MsgVisitor { 
public: 
    void visit(Msg1& m) { ... } // do stuff for Msg1 
    void visit(Msg2& m) { ... } 
}; 

void processMessage(MsgQueue *queue) 
{ 
    BaseMsg *msgToProcess = queue->pop(); 
    msgToProcess->accept(MsgProcessor()); 
    //delete msgToProcess; 
} 
+0

Dies zu tun, oder die obigen Vorschläge, würde eine massive Neuschreiben der Klassen/Strukturen, die im Bereich "tun Zeug", die nicht in einer logischen Hierarchie sind verwendet werden. Leider ist das alles alte Code, der über ein Dutzend Jahre alt ist und von einer anderen Gruppe gepflegt wird. Ich hatte die Aufgabe, die eingehenden Daten zu überarbeiten und diese an den Legacy-Code zu binden. Während ich diesen Code gerne neu schreiben würde, ist das momentan keine Option. :-( –

+0

@DanielJour Ihr Kommentar nicht einmal versuchen, die Frage zu beantworten, nur nit-Pick nicht relevante Elemente in einem massiv abgespeckte Beispiel. –

+0

@FlyboyWilson weshalb es war ein Kommentar und keine Antwort;) (Bereitstellung Feedback und Kritik ist eine der beabsichtigten Verwendungen von Kommentaren auf dieser Site) –

Verwandte Themen