2012-09-25 14 views
5

Ich versuche, ein Typsystem mit QSharedData zu machen. Die Idee ist einfach, es wird eine Reihe verschiedener Datentypen geben, von denen jeder aus der Basis-abstrakten Klasse abgeleitet wird. Ich möchte QSharedData verwenden, um die tatsächlichen Daten in jedem von ihnen zu speichern, aber jede der abgeleiteten Klassen wird verschiedene gespeicherte Daten haben. Ich versuche jetzt das grundlegendste Beispiel zu machen und einige Probleme zu haben.QSharedData und Vererbung

Sagen wir, das sind meine Basis rein virtuelle Klassen sind:

class cAbstractData: public QSharedData 
{ 
public: 
    cAbstractData(){ } 
    virtual int type() = 0; 
}; 

class cAbstractValue 
{ 
public: 
    cAbstractValue(){ } 
    virtual int type() = 0; 
protected: 
    QSharedDataPointer<cAbstractData>data_; 
}; 

die wir nun sagen, dass ich einen einzigen Wert für die Darstellung (als minmalistic Beispiel, das ist) eine Klasse machen wollen. Ich Ableitung die cAtomicValue von der Basiswertklasse, und ich bin auch eine Datenklasse Ableiten den Wert zu halten:

class cAtomicData:public cAbstractData 
{ 
public: 
    cAtomicData() { value_ = 0; } 
    int type(){ return 1; } 
    QVariant value_;//the actual value 
}; 

class cAtomicValue:public cAbstractValue 
{ 
public: 
    cAtomicValue() { 
     data_ = new cAtomicData;//creating the data object. 
    } 
    int type(){ return 1; } 
}; 

Jetzt in diesem Stadium es ganz gut, und im Debugger funktioniert, kann ich das Recht sehen Zeigertyp Aber jetzt möchte ich eine Funktion hinzufügen, um den Wert zu setzen und zu bekommen, und ich verstehe nicht, wie es geht. Nehmen wir den Setzer als Beispiel. Um den Wert einzustellen, müssen wir auf das value_ Mitglied der cAtomicData Klasse über das data_ Mitglied der cAtomicValue Klasse zugreifen. Da die data_ jedoch einen Basisklassenzeiger (cAbstractData) enthält, muss ich ihn irgendwie auf den richtigen Typ (cAtomicData) umwandeln. Ich habe versucht, dies zu tun:

template<class T> void set(T value) 
{ 
    static_cast<cAtomicData*>(data_.data())->value_ = value; 
} 

es offensichtlich nicht funktioniert, weil es detach() genannt und versucht, eine Kopie der Basisklasse zu machen, die es nicht können, da die Basisklasse rein virtuell ist. Dann habe ich versucht, den Zeiger zu werfen selbst:

static_cast<cAtomicData*>(data_)->value_ = value; 

aber ich bin immer einen invalid static_cast ... Fehler.

Wie mache ich das, und mache ich es sogar grundsätzlich richtig?

Antwort

2

Ich sehe keinen Weg, um zu erreichen, was Sie hier versuchen. Wie Sie festgestellt haben, muss QSharedDataPointer auf dem tatsächlichen Typ, den es enthält, templatiert werden.

Sie könnten Ihre Basisklasse zu einer Vorlage machen, z.

Aber ich bin mir nicht sicher, ich sehe, welchen Nutzen Sie daraus bekommen würden.

+1

Danke. Offensichtlich gibt es keinen Weg. Ich habe stattdessen 'QSharedPointer' verwendet. – SingerOfTheFall

5

Sie können zu QExplicitlySharedDataPointer statt QSharedDataPointer wechseln. Auf diese Weise wird detach() nicht aufgerufen, wenn Sie versuchen, einen nichtkonstanten Zeiger auf das Objekt cAbstractData zu erhalten. Dazu gehört das Umwandeln des Objekts QExplicitlySharedDataPointer<cAbstractData> in ein Objekt QExplicitlySharedDataPointer<cAtomicData>. Sie müssen jedoch detach() jedes Mal manuell aufrufen, wenn Sie eine Änderung an cAbstractData vornehmen möchten, wenn Sie Copy-on-Write verwenden möchten. Vielleicht können Sie eine Wrapper-Klasse schreiben, um das Trennen für Sie durchzuführen.

Dieses Verfahren kann unter Verwendung von mehr als QSharedPointer bevorzugt werden, da eine QExplicitlySharedDataPointer die gleiche Größe wie ein normaler Zeiger ist (und daher binäre compability hält), während ein QSharedPointer ist doppelt so groß (siehe this blog entry).

Edit: Beachten Sie, dass die Besetzung QExplicitlySharedDataPointer<cAbstractData>-QExplicitlySharedDataPointer<cAtomicData> statisch ist, so dass Sie garantieren müssen, dass das Objekt, das tatsächlich referenziert wird, ist ein Objekt vom Typ cAtomicData (oder eine Unterklasse) oder das Verhalten, wenn die Verwendung des Zeigers ist möglicherweise nicht definiert.

2

Ich hatte ein ähnliches Problem in meiner Anwendung und hier ist, wie ich es gelöst habe. Ich habe eine BaseClass, die mit dem Pimpl-Idiom implementiert ist und QExplicitlySharedDataPointer zeigt auf BaseClassPrivate. Diese Klasse wird von DerivedClass übernommen, deren privates Mitglied DerivedClassPrivateBaseClassPrivate erbt.

BaseClassPrivate hat ein Schwimmerelement namens baseParam und DerivedClassPrivate einen anderen Schwimmer Parameter derivedParam benannt hat.

löste ich dieses Problem die folgenden Schritte ausführen:

  1. einen geschützten Konstruktor definieren BaseClass(BaseClassPrivate* p)

    Diese verwendet, um neue abgeleitete Klassen mit einem Zeiger auf DerivedClassPrivate

  2. Definieren eines virtuellen clone() zu instanziiert Methode in beiden BaseClassPrivate und DerivedClassPrivate

    Diese Methode wird aufgerufen, um die private Klasse immer dann korrekt zu kopieren, wenn eine tiefe Kopie benötigt wird. Anstatt "QExplicitelySharedDataPointer :: detach()" aufzurufen, überprüfen wir, ob der QSharedData-Referenzzähler größer als 1 ist, und rufen dann clone auf. Bitte beachten Sie, dass sich QSharedData :: ref nicht in der Dokumentation befindet, so dass sich dies jederzeit ändern kann (auch wenn es unwahrscheinlich ist, dass es bald passiert).

  3. Static warf den d-Zeiger in DerivedClass

    ich es bequem finden eine private dCasted() Funktion zu definieren.

diese die virtuelle Funktion testen foo() in BaseClassPrivate und DerivedClassPrivate eingeführt wird, die entsprechend entweder baseParam oder derivedParam zurückgibt. Hier

ist der Code:

BaseClass.h

class BaseClass 
{ 
public: 
    BaseClass() : d(new BaseClassPrivate()) {} 
    BaseClass(const BaseClass& other) : d(other.d) {} 
    BaseClass& operator =(const BaseClass& other) {d = other.d; return *this;} 
    virtual ~BaseClass() {} 

    float baseParam() const {return d->baseParam;} 
    void setBaseParam(float value) { 
     detach(); // instead of calling d.detach() 
     d->baseParam = value; 
    } 

    float foo() const {return d->foo();} 

protected: 
    BaseClass(BaseClassPrivate* p) : d(p) {} 
    void detach() { 
     // if there's only one reference to d, no need to clone. 
     if (!d || d->ref == 1) return; // WARNING : d->ref is not in the official Qt documentation !!! 
     d = d->clone(); 
    } 
    QExplicitlySharedDataPointer<BaseClassPrivate> d; 
}; 

DerivedClass.h

class DerivedClass : public BaseClass 
{ 
public: 
    DerivedClass() : BaseClass(new DerivedClassPrivate()) {} 

    float derivedParam() const {return dCasted()->derivedParam;} 
    void setDerivedParam(float value) { 
     detach(); // instead of calling d.detach(); 
     dCasted()->derivedParam = value; 
    } 

private: 
    DerivedClassPrivate* dCasted() const {return static_cast<DerivedDataPrivate*>(d.data());} 
}; 

BaseClassPrivate.h

class BaseClassPrivate : public QSharedData 
{ 
public: 
    BaseClassPrivate() : QSharedData(), baseParam(0.0) {} 
    BaseClassPrivate(const BaseClassPrivate& other) : 
     QSharedData(other), baseParam(other.baseParam) {} 
    virtual ~BaseClassPrivate() {} 

    float baseParam; 
    virtual float foo() const {return baseParam;} 

    virtual BaseClassPrivate* clone() const { 
     return new BaseClassPrivate(*this); 
    } 
}; 

DerivedClassPrivate.h

class DerivedClassPrivate : public BaseClassPrivate 
{ 
public: 
    DerivedClassPrivate() : BaseClassPrivate(), derivedParam(0.0) {} 
    DerivedClassPrivate(const DerivedClassPrivate& other) : 
     BaseClassPrivate(other), derivedParam(other.derivedParam) {} 

    float derivedParam; 
    virtual float foo() const {return derivedParam;} 

    virtual BaseClassPrivate* clone() const { 
     return new DerivedClassPrivate(*this); 
    } 
}; 

Jetzt können wir Dinge tun, wie zum Beispiel:

Aufruf virtueller Funktionen:

DerivedClass derived; 
derived.setDerivedParam(1.0); 
QCOMPARE(derived.foo(), 1.0); // proving that DerivedClassPrivate::foo() is called 

Machen Sie Kopien von DerivedClass zu BaseClass richtig:

BaseClass baseCopy = derived; 
QCOMPARE(baseCopy.foo(), 1.0); // proving that DerivedClassPrivate::foo() is called 
           // even after copying to a BaseClass 

Machen Sie Kopien von BaseClass bis BaseClass res pecting die ursprüngliche Klasse und auch richtig ein copy-on-write machen:

BaseClass bbCopy(baseCopy);  // make a second copy to another BaseClass 
QCOMPARE(bbCopy.foo(), 1.0); // still calling DerivedClassPrivate::foo() 

// copy-on-write 
baseCopy.setBaseParam(2.0);  // this calls the virtual DerivedClassPrivate::clone() 
           // even when called from a BaseClass 
QCOMPARE(baseCopy.baseParam(), 2.0); // verify the value is entered correctly 
QCOMPARE(bbCopy.baseParam(), 1.0); // detach is performed correctly, bbCopy is 
             // unchanged 
QCOMPARE(baseCopy.foo(), 1.0); // baseCopy is still a DerivedClass even after detaching 

this helps

+0

Wäre das Aufrufen von setBaseParam() in der Basisklasse nicht eine tiefe Kopie erstellen, selbst wenn die Referenzzahl der privaten Daten 1 ist? Wäre es nicht besser, zuerst zu sehen, ob der Verweis über 1 ist, um clone() aufzurufen? – UndeadKernel

+0

Ja, du hast absolut recht. Ich habe darüber nachgedacht, als ich deinen Kommentar gesehen habe. Leider ist dies ein Nachteil, da es keine Möglichkeit gibt, die Referenzzählung mit QSharedData zu kennen. Ich denke, die Templating der Basisklasse, wie von Dan vorgeschlagen, ist der beste Weg zu gehen. –

+0

Ich schlage vor, einen Blick auf http://stackoverflow.com/questions/2693319/qexplicitmitlysharedpointer-and-inheritance –

0

Da Qt 4.5 Sie die ::clone() function für Ihre Art umsetzen können:

Diese Funktion ist vorausgesetzt, dass Sie "virtuelle Kopierkonstruktoren" für Ihre eigenen Typen unterstützen können. Um dies zu erreichen, sollten Sie eine Vorlage-Spezialisierung dieser Funktion für Ihre eigene Art, wie das Beispiel erklären unter:

template<> 
EmployeeData *QSharedDataPointer<EmployeeData>::clone() 
{ 
    return d->clone(); 
} 

In dem obigen Beispiel ruft die Template-Spezialisierung für den Klon() gibt die EmployeeData: : klon() virtuelle Funktion. Eine von EmployeeData abgeleitete Klasse könnte diese Funktion überschreiben und den richtigen polymorphen Typ zurückgeben.

Diese Funktion wurde in Qt 4.5 eingeführt.

Ich habe es getan und es funktioniert.

Entweder Ihre abstrakte Basisklasse und alle abgeleiteten Klassen müssen eine virtual BaseClass* clone() Funktion implementieren, die Sie von QSharedDataPointer::clone() nennen würden, oder Sie benötigen eine andere Methode (z Fabrik) eine neue Instanz mit dem gleichen Inhalt wie d zu erstellen.