Problem mit Software Architektur (C++)



  • Hallo zusammen
    Ich kämpfe gerade mit folgender amüsanten Fehlermeldung:

    1>c:\users\samuel\desktop\projekte\serenity\serenity\development\serenity\resourceservice.cpp(46) : error C2385: ambiguous access of 'GetName'
    1> could be the 'GetName' in base 'IService'
    1> or could be the 'GetName' in base 'IService'

    ähhm... genau! Schwieriges Dilemma, sich da zu entscheiden...

    Ich habe folgende Struktur:

    class IService{
    public:
     virtual const char *GetName(void) const = 0;
    };
    
    class IResourceService:public IService{
     // einige zusätzliche Dinge...
    };
    
    class AbstractService:public IService{
     // implentiert gewisse Verwaltungsaufgaben die in den allermeisten Services anfallen...
    };
    
    class ResourceService:public AbstractService,public IResourceService{
     const char *GetName(void) const;
    };
    

    Ist diese Architektur Schrott oder was? 😞

    Es ist mir schon klar, dass er natürlich im Prinzip zweimal von IService erbt (einmal durch den AbstractService und einmal durch den IResourceService). Das sollte doch aber kein Problem sein? :p

    Mfg Samuel


  • Mod

    Google mal nach Diamond of Death.



  • Ishildur schrieb:

    Es ist mir schon klar, dass er natürlich im Prinzip zweimal von IService erbt (einmal durch den AbstractService und einmal durch den IResourceService). Das sollte doch aber kein Problem sein?

    virtual erben.



  • @SeppJ

    Google mal nach Diamond of Death.

    Hmm, ich dachte, dieser sollte kein Problem darstellen, wenn man ausschliesslich von meheren Interfaces erbt (only pure virtual methods) ? Oder kann dies auch so Probleme verursachen?



  • Ishildur schrieb:

    @SeppJ

    Google mal nach Diamond of Death.

    Hmm, ich dachte, dieser sollte kein Problem darstellen, wenn man ausschliesslich von meheren Interfaces erbt (only pure virtual methods) ? Oder kann dies auch so Probleme verursachen?

    C++ ist nicht Java oder C#. Also ja, das verursacht in C++ auch Probleme. Da gibt es keine Unterscheidung.

    Grüssli



  • Also ja, das verursacht in C++ auch Probleme

    Was genau bedeutet das? Kannst du mir einige Beispiele geben, wo es Probleme macht und wie sich diese auswirken?



  • Ich kann nur mal mutmaßen was probleme machen könnte. Ob das wirklich so ist kann ich nicht sagen:

    C++ kennt keine reinen Interfaces. Auch wenn deine Interface Klasse nicht eine Methode implementiert, könnte dies der Fall sein. Erbst du dir also mehrere Klassen an deine Klassen ran die untereinander ebenfalls Erben. So erbst du ggf schon Implementierte Funktionen mit.

    class IWrong
    {
       DoWrong()
       {
          ....
       }
    }
    
    class IWrong1: IWrong
    {
    
    }
    
    class Wrong :public IWrong, IWrong1
    {
       //Hier haben wir auf einmal DoWrong 2 mal.
    }
    

    Ich hab sowas noch nicht versucht. Ob das wirklich das Problem ist kann ich nicht sagen. Ich könnte mir vorstellen das sowas Probleme auslösen kann.
    Wie schon gesagt, C++ kennt keine reinen interfaces, auch wenn ich Klassen Schreiben kann die der Definition nach ein Interface sind.



  • Sollte die virtuelle Verwerbung nicht genau dieses Problem beseitigen?



  • Ishildur schrieb:

    Sollte die virtuelle Verwerbung nicht genau dieses Problem beseitigen?

    👍



  • Das müsste man austesten was ist wenn deine Subklassen virtuell von deinen Interfaces erben. Ich kann aber gerade auch nicht hundertprozentig sagen was da passiert. Interessanterweise ist nämlich deine Basisklasse (Die welche von den Subklassen erbt) die Implementation des Virtuellen interfaces.
    Du hast also kein Diamond, sondern das sieht eher aus wie ein W.

    a
       / \
      /   \
     i1   i2
      \   /
       \ /
       Base //Das ist aber die Implementation von A
    

    Ich hab hier echt keine Ahnung was da nun genau passiert. Kann auch sein das es problemlos möglich ist.



  • Fedaykin schrieb:

    class IWrong
    {
       DoWrong()
       {
          ....
       }
    }
    
    class IWrong1: IWrong
    {
    
    }
    
    class Wrong :public IWrong, IWrong1
    {
       //Hier haben wir auf einmal DoWrong 2 mal.
    }
    

    Ich hab sowas noch nicht versucht. Ob das wirklich das Problem ist kann ich nicht sagen. Ich könnte mir vorstellen das sowas Probleme auslösen kann.

    Nö, das verursacht keine Probleme. Wieso sollte es auch? DoWrong() ist weder in IWrong1, noch in Wrong verfügbar.



  • Falls du "Effective C++" zu Hause hast, dann lies mal Item 40. Dort wird das Problem mit dem "deadly MI diamond".

    Versuch mal das hier:

    class IResourceService: virtual public IService{ 
     // einige zusätzliche Dinge... 
    }; 
    
    class AbstractService: virtual public IService{ 
     // implentiert gewisse Verwaltungsaufgaben die in den allermeisten Services anfallen... 
    };
    

    PS:
    Ich weiss nicht obs funktioniert 😉



  • Ishildur schrieb:

    Sollte die virtuelle Verwerbung nicht genau dieses Problem beseitigen?

    Hab ich ja gleich zu Beginn geschrieben. Daran liegt's und so lässt sich's lösen.



  • Es funktioniert so halbers. Er kompilierts zwar, jedoch nur wiederwillig:

    1>c:\users\samuel\desktop\projekte\serenity\serenity\development\serenity\ShaderResource.h(206) : warning C4250: 'PixelShaderResource' : inherits 'AbstractResource::AbstractResource::IsLoaded' via dominance
    1> c:\users\samuel\desktop\projekte\serenity\serenity\development\serenity\Resource.h(194) : see declaration of 'AbstractResource::IsLoaded'

    Er gibt ca. 5000 solche Warnungen aus...


  • Administrator

    Ishildur schrieb:

    Also ja, das verursacht in C++ auch Probleme

    Was genau bedeutet das? Kannst du mir einige Beispiele geben, wo es Probleme macht und wie sich diese auswirken?

    Ich wollte eigentlich darauf hinaus. Nehmen wir zum Beispiel C#:

    public interface IFoo
    {
      void bar();
    }
    
    public class Base
      : IFoo
    {
      public void bar() { }
    }
    
    public class Derived
      : Base, IFoo
    {
      // null problemo
    }
    

    Wenn du dies nun 1:1 auf C++ überträgst:

    class IFoo
    {
    public:
      virtual void bar() = 0;
    };
    
    class Base
      : public IFoo
    {
    public:
      void bar() { }
    };
    
    class Derived
      : public Base
      , public IFoo
    {
      // Nun hast du plötzlich zweimal die Funktion bar und eine davon ist rein virtuell
    };
    

    Und ja, wenn du von IFoo virtuell erbst, dann besteht dieses Problem nicht mehr:

    class IFoo
    {
    public:
      virtual void bar() = 0;
    };
    
    class Base
      : virtual public IFoo
    {
    public:
      void bar() { }
    };
    
    class Derived
      : public Base
      , virtual public IFoo
    {
      // Alles klar
    };
    

    Grüssli



  • Gibt es einen Grund, nicht einfach pauschal alles virtuell zu erben?



  • Ishildur schrieb:

    Es funktioniert so halbers. Er kompilierts zwar, jedoch nur wiederwillig:

    1>c:\users\samuel\desktop\projekte\serenity\serenity\development\serenity\ShaderResource.h(206) : warning C4250: 'PixelShaderResource' : inherits 'AbstractResource::AbstractResource::IsLoaded' via dominance
    1> c:\users\samuel\desktop\projekte\serenity\serenity\development\serenity\Resource.h(194) : see declaration of 'AbstractResource::IsLoaded'

    Er gibt ca. 5000 solche Warnungen aus...

    Prinzipiell ists erstmal eine Warnung. Ich denke wenn es genügend Interfaces existieren die über diese Virtuelle Vererbung arbeiten sind die Warnungen soviel.

    Ich kann aber nicht genau sagen ob es möglichkeiten gibt diese Warnungen weg zu bekommen.



  • Hehe, da kann Abhilfe geschaffen werden:

    // ###################### disable stupid and useless compiler - warnings ######################
    // --------------------------------------------------------------------------------------------
    #pragma warning(disable:4100) // unreferenced formal parameter [unused method parameters]
    #pragma warning(disable:4127) // conditional expression is constant [while(true)]
    #pragma warning(disable:4482) // nonstandard extension used: enum
    #pragma warning(disable:4706) // assignment within conditional expression
    #pragma warning(disable:4715) // not all control paths return a value
    #pragma warning(disable:4701) // potentially uninitialized local variable used
    #pragma warning(disable:4238) // nonstandard extension used : class rvalue used as lvalue
    #pragma warning(disable:4996) // 'stricmp': The POSIX name for this item is deprecated.
    #pragma warning(disable:4250) // inheritance via dominance (diamond of death with interfaces)
    // --------------------------------------------------------------------------------------------
    // ############################################################################################
    

    Dieser Block steht bei mir ziemlich weit oben in der main header datei. :p
    Siehe insbesondere die unterste Zeile...


  • Administrator

    Ishildur schrieb:

    Es funktioniert so halbers. Er kompilierts zwar, jedoch nur wiederwillig:

    1>c:\users\samuel\desktop\projekte\serenity\serenity\development\serenity\ShaderResource.h(206) : warning C4250: 'PixelShaderResource' : inherits 'AbstractResource::AbstractResource::IsLoaded' via dominance
    1> c:\users\samuel\desktop\projekte\serenity\serenity\development\serenity\Resource.h(194) : see declaration of 'AbstractResource::IsLoaded'

    Er gibt ca. 5000 solche Warnungen aus...

    Das heisst, dass du IsLoaded zweimal definiert hast, wegen der virtuellen Vererbung allerdings nur eines genommen wird. Daher womöglich die Funktionalität einer Klasse verändert werden könnte. Zeig mal kurz die Klassen und wie sie in Relation stehen, bzw. wer alles IsLoaded definiert. Das riecht ein wenig seltsam 😉

    Ishildur schrieb:

    Gibt es einen Grund, nicht einfach pauschal alles virtuell zu erben?

    Weil man es vielleicht gar nicht möchte? Man muss sich das immer ganz genau überlegen. Am besten verzichtet man aber auf solche Konstrukte gleich vollständig. Bei mir kommt sowas äusserst selten vor. Zudem kann die Performance etwas darunter leiden, wenn ich mich recht erinnere.

    Grüssli



  • @Dravere
    Die Methode IsLoaded ist 100% ausschliesslich in der Klasse AbstractResource definiert!!! 😉


Anmelden zum Antworten