Frage

Ich habe Code wie folgt:

class RetInterface {...}

class Ret1: public RetInterface {...}

class AInterface
{
  public:
     virtual boost::shared_ptr<RetInterface> get_r() const = 0;
     ...
};

class A1: public AInterface
{
  public:
     boost::shared_ptr<Ret1> get_r() const {...}
     ...
};

Dieser Code nicht kompiliert.

In Visual Studio hebt es

  

C2555: übergeordneter virtueller Funktion Rückgabetyp unterscheidet und nicht   covariant

Wenn ich nicht boost::shared_ptr verwenden, aber das Rück rohe Zeiger, der Code compiliert (Ich verstehe dies ist auf kovarianten Rückgabetypen in C ++). Ich kann sehen, das Problem ist, weil boost::shared_ptr von Ret1 nicht aus boost::shared_ptr von RetInterface abgeleitet ist. Aber ich möchte, für den Einsatz in anderen Klassen boost::shared_ptr von Ret1 zurückzukehren, sonst muss ich den zurückgegebenen Wert nach der Rückkehr gegossen.

  1. Mache ich etwas falsch?
  2. Wenn nicht, warum ist die Sprache, wie diese - es sollte erweiterbar sein Konvertierung zwischen Smart-Pointer in diesem Szenario zu behandeln? Gibt es eine wünschenswertere Abhilfe?
War es hilfreich?

Lösung

Erstens dies in der Tat ist, wie es in C ++ funktioniert: der Rückgabetyp einer virtuellen Funktion in einer abgeleiteten Klasse die gleichen sein wie in der Basisklasse sein. Es ist die besondere Ausnahme, dass eine Funktion, die einen Verweis / Zeiger zu einem gewissen Klasse X zurückgibt, kann durch eine Funktion außer Kraft gesetzt werden, die einen Verweis / Zeiger auf eine Klasse zurückgibt, die von X ableitet, aber wie Sie beachten Sie dies nicht zulässt smart Zeiger (wie shared_ptr), gerade für einfache Zeiger.

Wenn Sie Ihre Schnittstelle RetInterface umfassend genug ist, dann müssen Sie nicht den tatsächlichen zurück Typen in dem anrufenden Code kennen. Im Allgemeinen macht es keinen Sinn machen sowieso: Der Grund get_r ist eine virtual Funktion in erster Linie, weil man es durch einen Zeiger oder eine Referenz auf die Basisklasse AInterface Aufruf wird, in dem Fall, dass Sie nicht wissen können, was dem Typ abgeleiteten Klasse zurückkehren würde. Wenn Sie dies mit einer tatsächlichen A1 Referenz anrufen, können Sie einfach eine separate get_r1 Funktion in A1 erstellen, das tut, was Sie brauchen.

class A1: public AInterface
{
  public:
     boost::shared_ptr<RetInterface> get_r() const
     {
         return get_r1();
     }
     boost::shared_ptr<Ret1> get_r1() const {...}
     ...
};

Alternativ können Sie das Besuchermuster oder so etwas wie mein Dynamic Double Dispatch- Technik verwenden einen Rückruf in das zurückgegebene Objekt zu übergeben, die den Rückruf mit den richtigen Typ dann aufrufen kann.

Andere Tipps

Was ist mit dieser Lösung:

template<typename Derived, typename Base>
class SharedCovariant : public shared_ptr<Base>
{
public:

typedef Base BaseOf;

SharedCovariant(shared_ptr<Base> & container) :
    shared_ptr<Base>(container)
{
}

shared_ptr<Derived> operator ->()
{
    return boost::dynamic_pointer_cast<Derived>(*this);
}
};

z:

struct A {};

struct B : A {};

struct Test
{
    shared_ptr<A> get() {return a_; }

    shared_ptr<A> a_;
};

typedef SharedCovariant<B,A> SharedBFromA;

struct TestDerived : Test
{
    SharedBFromA get() { return a_; }
};

Sie können keine Rückgabetypen ändern (für Nicht-Zeiger, Nicht-Referenzrückgabetypen), wenn Methoden in C ++ Überlastung. A1::get_r müssen boost::shared_ptr<RetInterface> zurück.

Anthony Williams hat eine schöne umfassende beantworten .

Hier ist mein Versuch:

template<class T>
class Child : public T
{
public:
    typedef T Parent;
};

template<typename _T>
class has_parent
{
private:
    typedef char                        One;
    typedef struct { char array[2]; }   Two;

    template<typename _C>
    static One test(typename _C::Parent *);
    template<typename _C>
    static Two test(...);

public:
    enum { value = (sizeof(test<_T>(nullptr)) == sizeof(One)) };
};

class A
{
public :
   virtual void print() = 0;
};

class B : public Child<A>
{
public:
   void print() override
   {
       printf("toto \n");
   }
};

template<class T, bool hasParent = has_parent<T>::value>
class ICovariantSharedPtr;

template<class T>
class ICovariantSharedPtr<T, true> : public ICovariantSharedPtr<typename T::Parent>
{
public:
   T * get() override = 0;
};

template<class T>
class ICovariantSharedPtr<T, false>
{
public:
    virtual T * get() = 0;
};

template<class T>
class CovariantSharedPtr : public ICovariantSharedPtr<T>
{
public:
    CovariantSharedPtr(){}

    CovariantSharedPtr(std::shared_ptr<T> a_ptr) : m_ptr(std::move(a_ptr)){}

    T * get() final
   {
        return m_ptr.get();
   }
private:
    std::shared_ptr<T> m_ptr;
};

Und ein kleines Beispiel:

class UseA
{
public:
    virtual ICovariantSharedPtr<A> & GetPtr() = 0;
};

class UseB : public UseA
{
public:
    CovariantSharedPtr<B> & GetPtr() final
    {
        return m_ptrB;
    }
private:
    CovariantSharedPtr<B> m_ptrB = std::make_shared<B>();
};

int _tmain(int argc, _TCHAR* argv[])
{
    UseB b;
    UseA & a = b;
    a.GetPtr().get()->print();
}

Erläuterungen:

Diese Lösung impliziert Meta-Programme und die Klassen in covariant Smart-Pointer verwendet zu ändern.

Die einfache Vorlage struct Child ist hier die Art Parent und Vererbung zu binden. Jede Klasse erbt von Child<T> wird von T erbt und definiert T als Parent. Die Klassen in covariant Smart Pointer brauchen diese Art definiert werden.

Die Klasse has_parent wird verwendet bei der Kompilierung zu erkennen, ob eine Klasse des Typen Parent oder nicht definiert. Dieser Teil ist nicht von mir, habe ich den gleichen Code wie zu erkennen, ob ein Verfahren vorhanden ist ( siehe hier )

Wie wir Kovarianz mit Smart-Pointer, wir wollen unsere intelligenten Zeiger die bestehende Klasse-Architektur zu imitieren. Es ist einfacher zu erklären, wie es im Beispiel funktioniert.

Wenn ein CovariantSharedPtr<B> definiert ist, erbt es von ICovariantSharedPtr<B>, die als ICovariantSharedPtr<B, has_parent<B>::value> interpretiert wird. Wie B von Child<A> erbt, ist has_parent<B>::value wahr, so ICovariantSharedPtr<B> ist ICovariantSharedPtr<B, true> und erbt von ICovariantSharedPtr<B::Parent> die ICovariantSharedPtr<A> ist. Wie A keine Parent definiert hat, ist has_parent<A>::value falsch, ICovariantSharedPtr<A> ICovariantSharedPtr<A, false> ist und erbt von nichts.

Der wichtigste Punkt ist, wie Binherits von A, wir haben von ICovariantSharedPtr<B> ICovariantSharedPtr<A>inheriting. So jede Methode einen Zeiger oder einen Verweis auf ICovariantSharedPtr<A> Rückkehr kann durch ein Verfahren zurückkehrt das gleiche auf ICovariantSharedPtr<B> überlastet werden.

Es ist eine saubere Lösung in diese Blog-Post (von Raoul Borges)

Ein Auszug des Bits vor dem Hinzufügen von Unterstützung für mulitple Vererbung und abstrakte Methoden ist:

template <typename Derived, typename Base>
class clone_inherit<Derived, Base> : public Base
{
public:
   std::unique_ptr<Derived> clone() const
   {
      return std::unique_ptr<Derived>(static_cast<Derived *>(this->clone_impl()));
   }

private:
   virtual clone_inherit * clone_impl() const override
   {
      return new Derived(*this);
   }
};

class concrete: public clone_inherit<concrete, cloneable>
{
};

int main()
{
   std::unique_ptr<concrete> c = std::make_unique<concrete>();
   std::unique_ptr<concrete> cc = b->clone();

   cloneable * p = c.get();
   std::unique_ptr<clonable> pp = p->clone();
}

Ich möchte Sie ermutigen den ganzen Artikel zu lesen. Es ist einfach geschrieben und gut erklärt.

Herr Fooz beantwortet Teil 1 Ihrer Frage. Teil 2, funktioniert es auf diese Weise, da der Compiler nicht weiß, ob es AInterface :: get_r oder A1 :: get_r bei der Kompilierung aufrufen wird - es muss wissen, welche Rückgabewert um es bekommen wird, also es besteht auf beiden Verfahren die gleiche Art zurückkehrt. Dies ist Teil der C ++ Spezifikation.

Für das Problem zu umgehen, wenn A1 :: get_r einen Zeiger auf RetInterface zurückkehrt, werden die virtuellen Methoden in RetInterface noch wie erwartet, und das richtige Objekt wird gelöscht, wenn der Zeiger zerstört wird. Es gibt keine Notwendigkeit für verschiedene Rückgabetypen.

Vielleicht könnten Sie einen out-Parameter verwenden „Kovarianz mit zurück Schub shared_ptrs herum zu erhalten.

 void get_r_to(boost::shared_ptr<RetInterface>& ) ...

da ich ein Anrufer vermute in einem verfeinerten Shared_ptr Typ als Argument fallen kann.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top