Lösung des Diamond of Death Problem



  • Hey,
    ich bin schon öfters auf das Problem von gemeinsamen Base-Klassen von Base-Klassen gestoßen. Da habe ich mir folgende Lösung überlegt:

    struct Base
    {
    	int a;
    };
    template <typename BASE_CLASS>
    struct A
    {
    	void workOnA()
    	{
    		static_cast<BASE_CLASS*>(this)->a = 5;
    	}
    };
    template <typename BASE_CALSS>
    struct B
    {
    	void workOnA()
    	{
    		static_cast<BASE_CLASS*>(this)->a = 100;
    	}
    };
    struct C : Base, A<C>, B<C>
    {
    	using A<C>::workOnA;
    };
    

    Somit habe ich keine dynamische Bindung und kann statisch nach Base, A<C> oder B<C> casten. Dies wäre eine total einfache Lösung. Nur schreiben die meisten, dass man um das Diamond of Death Problem performant zu lösen, sein Design ändern sollte. Also was gibt es an meiner Lösung auszusetzen?



  • Mr.Long schrieb:

    ich bin schon öfters auf das Problem von gemeinsamen Base-Klassen von Base-Klassen gestoßen.

    Ich noch nie. Ich glaube, du machst was falsch.

    Mr.Long schrieb:

    Da habe ich mir folgende Lösung überlegt:

    Das ist keine Lösung, sondern ein Zeichen, dass du eigentlich gar keinen Diamand benötigst.

    Diamant heisst, dass A und B von Base erben, weil A und B von Base erben müssen.



  • Okay, dann noch das Beispiel zu dem Problem, welches ich lösen möchte:

    struct BasePacket
    {
    	template <unsigned int BYTE>
    	void setByte(char val)
    	{
    		a &= ~(0xFF << BYTE * 8);
    		a |= val << BYTE * 8;
    	}
    private:	
    	int a;
    };
    struct IpAddress : virtual BasePacket
    {
    	void setIpAddress(char val)
    	{
    		setByte<0>(val);
    	}
    };
    struct MacAdderss : virtual BasePacket
    {
    	void setMacAddress(char val)
    	{
    		setByet<1>(val);
    	}
    };
    struct Action : virtual BasePacket
    {
    	void setAction(char val)
    	{
    		setByte<2>(val);
    	}
    };
    struct Packet1 : virtual IpAddress , virtual MacAdderss
    {
    };
    struct Packet2 : virtual IpAddress , virtual MacAdderss, virtual Action
    {
    }
    int main()
    {
    }
    


  • Was soll das sein? Sieht für mich nach völligem Quatsch aus.

    Eine IP Adresse *ist* ein BasePacket? Unsinn!
    Eine IP Adresse *hat* doch noch nicht mal ein BasePacket.
    Eher noch hat ein Paket eine Adresse.

    Davon abgesehen ist das doch wohl ein ad-hoc für das Forum erfundenes Beispiel, ja? => Uninteressant. Bring was konkretes, dann kann man darüber reden.



  • Ich hatte mal das eher gelöst mit Paket , einem op[] und feinen Proxies Handle<IPAddress,13> SRC_IP , sodaß p[SRC_IP]=srcAddress ging, wobei man auch Zugriffe von Hinten oder auf 6-Bytes oder einzelne Bits definieren konnte. Hat mir sehr gefallen. Paketlayout war bitgenau wie in den RFCs.

    Im Übrigen war immer klar, in welcher Schicht man war. Ist man nicht gerade bei den ARP-Funktionen, wie kann dann ein Paket IP und MAC haben? Es hat nur MAC und irgendwo drin den Payload.

    Mr.Long schrieb:

    Somit habe ich keine dynamische Bindung und kann statisch nach Base, A<C> oder B<C> casten. Dies wäre eine total einfache Lösung.

    Nur weiß ich nicht, was sie bezweckt. Bloß ein wenig Speed?

    Mr.Long schrieb:

    Nur schreiben die meisten, dass man um das Diamond of Death Problem performant zu lösen, sein Design ändern sollte. Also was gibt es an meiner Lösung auszusetzen?

    Was gibt es für Gründe, es so zu machen? Ist das überhaupt tragfähig, um das genaze Projekt dann damit zu machen?

    Wenn Du sowas siehst

    struct IPPacket : virtual Version, virtual IHL, virtual TOS, 
       virtual TotalLength, virtual Identification, virtual Flags, 
       virtual FragmentOffset, virtual TTL, virtual Protocol, 
       virtual IPAddress, virtual IPAddress,//Oh, Mist, SRC und DST
       virtual HeaderChecksum, virtual<Optional<OptionsAndPadding>>, 
       virtual Data
    {
    };
    

    musst Du dann auch so fürchterlich weinen?

    Der Ethernet-Frame hat dann selber auch noch ein paar Felder und als Nutzlast dieses IP-Paket. Willste wirklich?

    struct EtherPacket : 
       virtual MacAddress, virtual MacAddress//Oh, Mist, SRC und DST
       ..., 
       virtual IPPacket//so richtig? 
    {
    };
    

    Also mir erschließt sich der Zweck nicht ganz. Ich vermute, ich würde diese Klassen ungern benutzen.



  • struct EtherPacket :
    virtual MacAddress, virtual MacAddress//Oh, Mist, SRC und DST
    

    Würde so ja nicht aussehen, sondern so:

    struct EtherPacket :
    virtual MacAddress<0>, virtual MacAddress<4>
    

    Wobei der Templateübergabeparameter dem Byteoffset im Packet entspricht.
    Meine Idee ist eben, aus Implementationen verschiedener Funktionen mir ein neues Packet basteln zu können.

    Das ganze habe ich mir ausgedacht, als ich ein Protokoll implementiert habe, welches viele verschiedene Packete hat, diese aber oft selbe Daten, nur an verschiedenen Stellen im Packet bearbeiten sollten.

    Ich konnte das dann einfach so schreiben:

    stuct PacketName1 : virtual PacketItemIpAddress<0>, virtual PacketItemIpAddress<4>, virtual PacketItemMacAddress<8>, virtual PacketItemMacAddress<12>, virtual PacketItemHash<16>, virtual PacketItemDLC<17> // , usw.
    {
    };
    struct PacketName2 : virtual PacketItemIpAddress<0>, virtual PacketItemHash<4>
    {
    };
    int main()
    {
        PacketName1 packet1;
        PacketName2 packet2;
        packet1.setHash(4023); // setze Daten für Hash ab Byte 16
        packet2.setHash(5555); // setze Daten für Hash ab Byte 4
    }
    

    Diese Lösung fand ich damals sehr cool, nur dass ich es mit virtueller Vererbung gelöst hatte und z.B. IpAddresse von BasePacket erben musste um an die Adresse des Packets bzw. der Funktionen von BasePacket zu kommen, hat mir nicht so gefallen. Allerdings schreibt hustbaer jetzt auch, dass diese Hirachie keinen Sinn macht, da z.B. IpAddress kein Packet ist, womit er eigentlich auch Recht hat und somit alles auf einen Designfehler hindeutet. Vielleicht denke ich da auch einfach zu Funktional.
    Sprechen wir dann jetzt überhaupt vom Diamond of Death?

    Auf jeden Fall wollte ich die virtuelle Vererbung mit der in meiner ersten Post gezeigten Lösung weg machen.
    Sollte man das ganze anders lösen? Bzw. wie könnte ein anderer Lösungsansatz aussehen? Ich will halt nichts 2 mal schreiben oder auf Klassen verzichten müssen.



  • Mr.Long schrieb:

    Vielleicht denke ich da auch einfach zu Funktional.

    *lach*

    Mr.Long schrieb:

    Sprechen wir dann jetzt überhaupt vom Diamond of Death?

    Nein. Darüber werde ich auch nicht reden.

    Mr.Long schrieb:

    Sollte man das ganze anders lösen? Bzw. wie könnte ein anderer Lösungsansatz aussehen? Ich will halt nichts 2 mal schreiben oder auf Klassen verzichten müssen.

    IpAddress muss zwar ein Typ werden, aber gleich eine Klasse?

    Was mir wichtig wäre, daß SrcIP und DstIp echt verschiedene Namen haben und ich nicht Ip<4> und Ip<12> schreiben muss. Also ich will in den Funktionen das Paketlayout vergessen können.

    Mr.Long schrieb:

    packet1.setHash(4023); // setze Daten für Hash ab Byte 16
        packet2.setHash(5555); // setze Daten für Hash ab Byte 4
    

    Genau.

    Allerdings brauche ich es nicht, daß der Compiler das Paket (den Paketkopf) selber zusammenbaut, ich weiß selber, wie groß es ist. Außerdem wollte ich einzelne Bits ansprechen können. Und ich wollte unterschiedlich drauf zugreifen können, so wier als unionm zum Beispiel zum Versenden siehts für mich aus wie ein langes Array of Bytes. Darum

    p1[SRC_IP]=5;
    p2[MAC_IP]=8;
    
    #include <iostream>
    using namespace std;
    
    struct IpAddress{
    	int v;
    	void set(int v){
    		this->v=v;
    	}
    };
    struct SrcIpAddress:IpAddress
    {
    };
    struct DstIpAddress:IpAddress
    {
    };
    
    struct MacAddress
    {
    	char v;
        void set(char v){
    		this->v=v;
        }
    };
    struct Packet1 : SrcIpAddress, DstIpAddress, MacAddress
    {
    };
    struct Packet2 : MacAddress
    {
    };
    int main()
    {
    	Packet1 p1,p2;
    	p1.SrcIpAddress::set(5);
    	p2.MacAddress::set(7);
    }
    

    Oder, hey, funktional, in diese Richtung

    #include <iostream>
    using namespace std;
    
    template<int size>
    struct BasePacket{
    	char data[size];
    };
    template<typename Base,int pos>
    struct IpAddress:Base{
    	void set(int v){
    		Base::data[pos]=v;
    	}
    };
    template<typename Base,int pos>
    struct SrcIpAddress:IpAddress<Base,pos>
    {
    	void setSrcIpAddress(int v){
    		Base::set(v);
    	}
    };
    template<typename Base,int pos>
    struct DstIpAddress:IpAddress<Base,pos>
    {
    	void setDstIpAddress(int v){
    		Base::set(v);
    	}
    };
    template<typename Base,int pos>
    struct MacAddress:Base{
        void set(char v){
    		Base::data[pos]=v;
        }
        void setMacAddress(char v){
    		set(v);
        }
    };
    
    struct Tmp1:BasePacket<20>{
    };
    struct Tmp2:SrcIpAddress<Tmp1,4>{
    };
    struct Tmp3:DstIpAddress<Tmp2,12>{
    };
    struct Packet1:Tmp3{
    };
    struct Tmp4:BasePacket<8>{
    };
    struct Tmp5:MacAddress<Tmp4,6>{
    };
    struct Packet2:Tmp5{
    };
    
    int main()
    {
    	Packet1 p1;
    	Packet2 p2;
    	p1.setSrcIpAddress(5);
    	p2.setMacAddress(7);
    }
    

    Die macht mich aber wieder traurig.



  • Ok, ich verstehe jetzt ein bischen mehr worauf ich achten muss.


Anmelden zum Antworten