Avatar billede codecow Nybegynder
05. marts 2005 - 18:49 Der er 17 kommentarer og
1 løsning

Diamant formet nedarvning

Hej alle

Jeg er ved at lave et client server system. Hvert object kan være på serveren og på klienten men på serveren skal objektet understøtte nogle flere funktioner end på klienten.

Da jeg på server siden gerne vil kunne gøre det der skal gøres fra  en "ServerObject" pointer, er "ServerObject" nedarvet fra "Object"

For at server funktionerne ikke kan kalde på klient siden har jeg en abstrakt base klasse, "Object", og en abstrakt child klasse "ServerObject" som nedarver fra "Object". Implementeringerne "ObjSomething" og "SvrObjSomething" nedarver fra hhv "Object" og "ServerObject".

Det er en såkaldt diamant formet nedarvning

      Object
    ^      ^
  /        \
ObjSome  ServerObject
  ^        ^
  \        /
    SvrObjSome

(Ok ascii er ikke lige til at lave UML i :)

Problemet er at det giver mig flg. advarsel

: warning C4250: 'SvrObjSomething' : inherits 'ObjSomething::func1' via dominance

Er der en måde at undgå denne advarsel, dvs. ikke "#pragma warning ( disable : 4250 )" men en måde at tydeliggøre overfor compileren hvad det er jeg vil have den til ?

Nedenstående kode illustrere problemet

////////////////////////////////////////////////////
#include "stdafx.h"
#include <iostream>
#include <conio.h>
// #pragma warning ( disable : 4250 )

class Object {
public:
    virtual void func1() = 0;
};

class ServerObject : virtual public Object {
public:
    virtual void func2() = 0;
};

class ObjSomething : virtual public Object {
public:
    void func1() {
        std::cout<<"func1\n";
        return;
    }
};

class SvrObjSomething : public ObjSomething, public ServerObject {
public:
    void func2() {
        std::cout<<"func2\n";
        return;
    }
};

int main( int argc, char* argv[] ) {

    ServerObject* ptrObj;

    SvrObjSomething* ptrSvrObjSomething = new SvrObjSomething;
    ptrObj = (ServerObject*) ptrSvrObjSomething;

    // Use the object
    ptrObj ->func1();
    ptrObj ->func2();

    // Clean up
    delete ptrObj;
    while( ! kbhit());
    return 0;
}
Avatar billede arne_v Ekspert
05. marts 2005 - 19:03 #1
Den der konstruktion er kendt som "Diamond of death".

Grundliggende siger den warning at:

* SvrObjSomething har egentligt 2 func1 fordi både ObjSomething og ServerObject har en

* men da ServerObject's func1 kommer fra Object og ObjSomething's func1 har overidet
  samme så er det altså ObjSomething's func1 der bliver brugt
Avatar billede arne_v Ekspert
05. marts 2005 - 19:04 #2
Jeg tror at du skal genoverveje designet !
Avatar billede codecow Nybegynder
05. marts 2005 - 19:11 #3
OK. Ja det har også drillet lidt.

Har du en god ide til hvordan man kan redesigne klasserne ?

Jeg er bare interesseret i at server funktionaliteten ikke kan kaldes på klient siden, og at jeg kan kalde funktionaliteten i både objetet og serverobjektet fra en ServerObject pointer.

Mange tak for hjælpen indtil vidre :)

Henrik
Avatar billede arne_v Ekspert
05. marts 2005 - 19:20 #4
Hvis ServerObject ikke indeholder noget, så er det jo nemt:

Object
  |
ObjSomething
  |
SvrObjSomething
Avatar billede codecow Nybegynder
05. marts 2005 - 19:24 #5
ja, men SvrObjSomething skal tvinges til at implementere et antal funktioner, og jeg vil gerne undgå at der ikke er implmenteret en af funktionerne eller at man f.eks er kommet til at implementere "readDataBase" men serveren vil have "ReadDatabase"
Avatar billede codecow Nybegynder
05. marts 2005 - 19:26 #6
Altså tvinge ObjSomething til at implementere et givet interface og ligeledes for SvrObjSomething.
Avatar billede arne_v Ekspert
05. marts 2005 - 19:28 #7
Kan du ikke lave det som:

class Object
  virtual = 0
  fælles implementation

class IClient
  virtual = 0

class IServer
  virtual = 0

class ObjClient : public Object, public IClient
  client specifik implementation

class ObjServer : public Object, public IServer
  server specifik implementation
Avatar billede codecow Nybegynder
05. marts 2005 - 19:57 #8
Det kan være at jeg skal forklare mig nærmere.

Klient objektet ObjSomething implementere funktionere "toXML" og "fromXML", SvrObjSomething implemntere funktioneren "ReadDatabase" og "WriteDatabase" dvs, at jeg
Skal kunne kalde client funktionalitet på server siden.

hvis jeg implmentere på denne måde får jeg at vide at ": error C2039: 'fromXML' : is not a member of 'ServerObject'"

Da jeg gerne vil kunne lave en general funktion til at læse og skrive databasen vil jeg gerne specificere for compileren at pointeren kan tilgå funktionalitet i både klient og server objektet. Såsom:

    ServerObject* ptrObj;

    // This happens in a lookup function
    SvrObjSomething* ptrSvrObjSomething = new SvrObjSomething;
    ptrObj = (ServerObject*) ptrSvrObjSomething;

    // Use the object
    ptrObj ->fromXML();
    ptrObj ->writeDatabase();


////////////////////////////////////////////////////////////

class ClientObject {
public:
    virtual void toXML() = 0;
    virtual void fromXML() = 0;
};

class ServerObject {
public:
    virtual void readDatabase() = 0;
    virtual void writeDatabase() = 0;
};

class ObjSomething : public ClientObject {
public:
    void toXML() {
        std::cout<<"toXML\n";
    }

    void fromXML() {
        std::cout<<"fromXML\n";
    }
};

class SvrObjSomething : public ObjSomething, public ServerObject {
public:
    void readDatabase() {
        std::cout<<"readDatabase\n";
    }

    void writeDatabase() {
        std::cout<<"writeDatabase\n";
    }
};

int main( int argc, char* argv[] ) {

    ServerObject* ptrObj;

    // This happens in a lookup function
    SvrObjSomething* ptrSvrObjSomething = new SvrObjSomething;
    ptrObj = (ServerObject*) ptrSvrObjSomething;

    // Use the object
    ptrObj ->fromXML();
    ptrObj ->writeDatabase();

    // Clean up
    delete ptrObj;
    while( ! kbhit());
    return 0;
}
Avatar billede codecow Nybegynder
05. marts 2005 - 20:08 #9
Ok. Måske skal jeg bare lave en workaround som dette :)

    ClientObject* ptrCli;
    ServerObject* ptrSvr;

    // This happens in a lookup function
    SvrObjSomething* ptrSvrObjSomething = new SvrObjSomething;
    ptrSvr = (ServerObject*) ptrSvrObjSomething;
    ptrCli = (ClientObject*) ptrSvrObjSomething;

    // Use the object
    ptrCli -> fromXML();
    ptrSvr ->writeDatabase();

    // Clean up
    delete ptrSvr;
    delete ptrCli;
Avatar billede arne_v Ekspert
05. marts 2005 - 20:38 #10
class ServerObject {
public:
    virtual void toXML() = 0;
    virtual void fromXML() = 0;
    virtual void readDatabase() = 0;
    virtual void writeDatabase() = 0;
};
Avatar billede arne_v Ekspert
05. marts 2005 - 20:43 #11
eller

class ServerObject : public ClientObject {
public:
    virtual void readDatabase() = 0;
    virtual void writeDatabase() = 0;
};
Avatar billede codecow Nybegynder
05. marts 2005 - 20:54 #12
doohhhhh!!!!!

nå ja. Tak :)

class ServerObject {
public:
    virtual void toXML() = 0;
    virtual void fromXML() = 0;
    virtual void readDatabase() = 0;
    virtual void writeDatabase() = 0;
};

Er nok den kønneste løsning.
Men

class ServerObject : public ClientObject {
public:
    virtual void readDatabase() = 0;
    virtual void writeDatabase() = 0;
};


Vil du have point så lav et svar.

Som en biting, hvad er det der er så farligt ved diamant formet nedarvning ?

Object og ServerObject i den oprindelige koder er jo abstrakte klasser så de kan jo aldrig blive instanscieret.
Avatar billede arne_v Ekspert
05. marts 2005 - 20:56 #13
svar
Avatar billede codecow Nybegynder
05. marts 2005 - 20:57 #14
ups. trykkede på send.

class ServerObject : public ClientObject {
public:
    virtual void readDatabase() = 0;
    virtual void writeDatabase() = 0;
};

Vil jo bare give den oprindelige arkitektur.
Avatar billede arne_v Ekspert
05. marts 2005 - 20:57 #15
Det er utroligt svært at gennemskue logikken når der er 2 forekomster af members
fra "top" klassen.
Avatar billede arne_v Ekspert
05. marts 2005 - 20:59 #16
Prøv og gæt på hvad det her program udskriver:

#include <iostream>

using namespace std;

class Number
{
  private:
      int num;
  public:
      Number() { num=0; };
      void SetNum(int n) { num = n; };
      int GetNum() { return num; };
};

class IncrOneNumber : public Number
{
  public:
      IncrOneNumber() : Number() { };
      void IncrOne() { SetNum(GetNum()+1); };
      void print() { cout << GetNum() << endl; };
};

class IncrFiveNumber : public Number
{
  public:
      IncrFiveNumber() : Number() { };
      void IncrFive() { SetNum(GetNum()+5); };
      void write() { cout << GetNum() << endl; };
};

class IncrOneAndFiveNumber : public IncrOneNumber, public IncrFiveNumber
{
  public:
      IncrOneAndFiveNumber() : IncrOneNumber(), IncrFiveNumber() { };
};

int main()
{
  IncrOneAndFiveNumber n;
  n.IncrOne();
  n.IncrFive();
  n.print();
  n.write();
  return 0;
}
Avatar billede codecow Nybegynder
05. marts 2005 - 21:31 #17
1,5. Ja ok. Det er ikke lige til at se, men det er det "virtual" keywordet er til for.

class IncrOneNumber : virtual public Number
class IncrFiveNumber : virtual public Number

Løser problemet. Så udskriver den 6,6 som forventet.

Problemet her er jo at der er to instancer af "Number". Ikke at der er dobbelt tydeligehed af funktioner.

Anyway. Point taken. Kan godt se at det er noget ondt snask at rode sig ud i.

Tak for hjælpen.
Avatar billede arne_v Ekspert
05. marts 2005 - 21:55 #18
virtual gør at du kun får et Number men det er jo heller ikke altid
løsningen.

Modificeret eksempel:

#include <iostream>

using namespace std;

class Number
{
  private:
      int num;
      int calls;
  public:
      Number() { num=0; calls=0; };
      void SetNum(int n) { num = n; };
      int GetNum() { return num; };
      void IncrCalls() { calls++; };
      int GetCalls() { return calls; };
};

class IncrOneNumber : virtual public Number
{
  public:
      IncrOneNumber() : Number() { };
      void IncrOne() { SetNum(GetNum()+1); };
      void print() { IncrCalls(); cout << GetNum() << " (print called " << GetCalls() << " times)" << endl; };
};

class IncrFiveNumber : virtual public Number
{
  public:
      IncrFiveNumber() : Number() { };
      void IncrFive() { SetNum(GetNum()+5); };
      void write() { IncrCalls(); cout << GetNum() << " (write called " << GetCalls() << " times)" << endl; };
};

class IncrOneAndFiveNumber : public IncrOneNumber, public IncrFiveNumber
{
  public:
      IncrOneAndFiveNumber() : IncrOneNumber(), IncrFiveNumber() { };
};

int main()
{
  IncrOneAndFiveNumber n;
  n.IncrOne();
  n.IncrFive();
  n.print();
  n.write();
  n.print();
  n.write();
  return 0;
}
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester