Question

J'ai un code comme celui-ci:

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 {...}
     ...
};

Ce code ne compile pas.

Dans Visual Studio, il soulève

  

C2555: le type de renvoi de fonction virtuelle prioritaire est différent et non différent   covariant

Si je n'utilise pas boost::shared_ptr mais renvoie des pointeurs bruts, le code est compilé (je comprends que cela est dû à types de retour covariants en C ++). Je peux voir que le problème vient du fait que Ret1 de RetInterface n'est pas dérivé de <=> de <=>. Mais je veux retourner <=> sur <=> pour l’utiliser dans d’autres classes, sinon je dois convertir la valeur renvoyée après le retour.

  1. Est-ce que je fais quelque chose de mal?
  2. Si ce n'est pas le cas, pourquoi le langage ressemble-t-il à cela? Il devrait être extensible pour gérer la conversion entre les pointeurs intelligents dans ce scénario. Existe-t-il une solution de contournement souhaitable?
Était-ce utile?

La solution

En premier lieu, voici comment cela fonctionne en C ++: le type de retour d'une fonction virtuelle dans une classe dérivée doit être identique à celui de la classe de base. Il existe une exception spéciale: une fonction qui retourne une référence / un pointeur vers une classe X peut être remplacée par une fonction qui renvoie une référence / un pointeur vers une classe dérivée de X, mais si vous notez que cela ne permet pas pointeurs intelligents (tels que shared_ptr), uniquement pour les pointeurs simples.

Si votre interface RetInterface est suffisamment complète, vous n'avez pas besoin de connaître le type renvoyé dans le code appelant. En général, cela n’a aucun sens: la raison pour laquelle get_r est une fonction virtual, c’est parce que vous l’appellerez via un pointeur ou une référence à la classe de base AInterface, auquel cas vous pouvez 'sais pas quel type la classe dérivée retournerait. Si vous appelez ceci avec une référence A1 réelle, vous pouvez simplement créer une get_r1 fonction distincte dans <=> qui fait ce dont vous avez besoin.

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

Vous pouvez également utiliser le modèle de visiteur ou quelque chose comme ma technique Dispatch dynamique . pour renvoyer un rappel à l'objet renvoyé, qui peut ensuite l'invoquer avec le type correct.

Autres conseils

Qu'en est-il de cette solution:

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);
}
};

exemple:

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_; }
};

Vous ne pouvez pas modifier les types de retour (pour les types de retour non-pointeur ou non référencé) lors d'une surcharge de méthodes en C ++. A1::get_r doit renvoyer un boost::shared_ptr<RetInterface>.

Anthony Williams a une réponse complète et agréable.

Voici ma tentative:

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;
};

Et un petit exemple:

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();
}

Explications:

Cette solution implique une méta-programmation et la modification des classes utilisées dans les pointeurs intelligents covariants.

La structure de modèle simple Child permet de lier le type Parent et l'héritage. Toute classe héritant de Child<T> héritera de T et définira has_parent comme CovariantSharedPtr<B>. Les classes utilisées dans les pointeurs intelligents covariants nécessitent la définition de ce type.

La classe ICovariantSharedPtr<B> est utilisée pour détecter au moment de la compilation si une classe définit le type ICovariantSharedPtr<B, has_parent<B>::value> ou non. Cette partie n’est pas la mienne, j’ai utilisé le même code que celui utilisé pour détecter l’existence d’une méthode ( voir ici )

Comme nous voulons une covariance avec des pointeurs intelligents, nous voulons que nos pointeurs intelligents imitent l’architecture de classe existante. Il est plus facile d’expliquer comment cela fonctionne dans cet exemple.

Lorsqu'un B est défini, il hérite de Child<A>, ce qui est interprété comme has_parent<B>::value. Comme ICovariantSharedPtr<B, true> hérite de ICovariantSharedPtr<B::Parent>, ICovariantSharedPtr<A> est vrai, ainsi A correspond à has_parent<A>::value et hérite de ICovariantSharedPtr<A, false> qui est <=>. Comme <=> n’a pas <=> défini, <=> est faux, <=> est <=> et hérite de rien.

Le point principal est que <=> hérite de <=>, nous avons <=> héritant de <=>. Ainsi, toute méthode renvoyant un pointeur ou une référence sur <=> peut être surchargée par une méthode renvoyant la même chose sur <=>.

Il existe une solution soignée publiée dans cet article de blog (de Raoul Borges)

Un extrait du bit avant l'ajout du support pour l'héritage multiple et les méthodes abstraites est:

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();
}

Je vous encourage à lire l'article complet. C'est simplement écrit et bien expliqué.

M. Fooz a répondu à la première partie de votre question. La partie 2 fonctionne de cette façon car le compilateur ne sait pas s'il appellera AInterface :: get_r ou A1 :: get_r au moment de la compilation - il doit savoir quelle valeur de retour il va obtenir. Il insiste donc sur les deux méthodes retournant le même type. Cela fait partie de la spécification C ++.

Pour la solution de contournement, si A1 :: get_r renvoie un pointeur sur RetInterface, les méthodes virtuelles de RetInterface fonctionneront toujours comme prévu et l'objet approprié sera supprimé lorsque le pointeur sera détruit. Il n'y a pas besoin de types de retour différents.

peut-être pourriez-vous utiliser un paramètre out pour contourner & "la covariance avec boost retourné_ptrs.

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

étant donné que je soupçonne qu'un appelant peut déposer un argument plus raffiné en shared_ptr.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top