Domanda

Ho un codice come questo:

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

Questo codice non viene compilato.

In Visual Studio aumenta

  

C2555: il tipo di ritorno della funzione virtuale di sostituzione differisce e non lo è   covariante

Se non uso boost::shared_ptr ma restituisco puntatori non elaborati, il codice viene compilato (ho capito che ciò è dovuto a tipi di ritorno covarianti in C ++). Vedo che il problema è perché Ret1 di RetInterface non deriva da <=> di <=>. Ma voglio restituire <=> di <=> per l'uso in altre classi, altrimenti devo lanciare il valore restituito dopo il ritorno.

  1. Sto facendo qualcosa di sbagliato?
  2. In caso contrario, perché la lingua è così: dovrebbe essere estensibile per gestire la conversione tra i puntatori intelligenti in questo scenario? C'è una soluzione desiderabile?
È stato utile?

Soluzione

In primo luogo, è così che funziona in C ++: il tipo restituito di una funzione virtuale in una classe derivata deve essere lo stesso della classe base. C'è la speciale eccezione che una funzione che restituisce un riferimento / puntatore a una classe X può essere sovrascritta da una funzione che restituisce un riferimento / puntatore a una classe che deriva da X, ma come noti questo non consente puntatori intelligenti (come shared_ptr), solo per puntatori semplici.

Se l'interfaccia RetInterface è sufficientemente completa, non sarà necessario conoscere il tipo restituito effettivo nel codice chiamante. In generale non ha comunque senso: il motivo get_r è in primo luogo una funzione virtual è perché la chiamerai tramite un puntatore o un riferimento alla classe base AInterface, nel qual caso puoi Non so quale tipo restituirebbe la classe derivata. Se lo chiami con un effettivo A1 riferimento, puoi semplicemente creare una get_r1 funzione separata in <=> che fa quello che ti serve.

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

In alternativa, puoi utilizzare il modello di visitatore o qualcosa del genere della mia Dynamic Double Dispatch per trasferire un callback all'oggetto restituito che può quindi richiamare il callback con il tipo corretto.

Altri suggerimenti

Che dire di questa soluzione:

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

per esempio:

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

Non è possibile modificare i tipi di restituzione (per i tipi di restituzione senza puntatore e senza riferimento) quando si sovraccaricano i metodi in C ++. A1::get_r deve restituire un boost::shared_ptr<RetInterface>.

Anthony Williams ha una bella risposta completa .

Ecco il mio tentativo:

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

E un piccolo esempio:

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

Spiegazioni:

Questa soluzione implica la meta-programmazione e la modifica delle classi utilizzate nei puntatori intelligenti covarianti.

La semplice struttura del modello Child è qui per legare il tipo Parent e l'ereditarietà. Qualsiasi classe che eredita da Child<T> erediterà da T e definirà has_parent come CovariantSharedPtr<B>. Le classi utilizzate nei puntatori intelligenti covarianti richiedono che questo tipo sia definito.

La classe ICovariantSharedPtr<B> viene utilizzata per rilevare in fase di compilazione se una classe definisce o meno il tipo ICovariantSharedPtr<B, has_parent<B>::value>. Questa parte non è mia, ho usato lo stesso codice per rilevare se esiste un metodo ( vedi qui )

Poiché desideriamo la covarianza con i puntatori intelligenti, vogliamo che i nostri puntatori intelligenti imitino l'architettura di classe esistente. È più facile spiegare come funziona nell'esempio.

Quando viene definito un B, eredita da Child<A>, che viene interpretato come has_parent<B>::value. Poiché ICovariantSharedPtr<B, true> eredita da ICovariantSharedPtr<B::Parent>, ICovariantSharedPtr<A> è vero, quindi A è has_parent<A>::value ed eredita da ICovariantSharedPtr<A, false> che è <=>. Poiché <=> non ha <=> definito, <=> è falso, <=> è <=> ed eredita dal nulla.

Il punto principale è che <=> eredita da <=>, abbiamo <=> ereditando da <=>. Pertanto, qualsiasi metodo che restituisce un puntatore o un riferimento su <=> può essere sovraccaricato da un metodo che restituisce lo stesso su <=>.

Esiste una soluzione ordinata pubblicata in questo post sul blog (da Raoul Borges)

Un estratto del bit prima di aggiungere il supporto per eredità multipla e metodi astratti è:

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

Vorrei incoraggiare a leggere l'articolo completo. È semplicemente scritto e ben spiegato.

Mr Fooz ha risposto alla prima parte della tua domanda. Parte 2, funziona in questo modo perché il compilatore non sa se chiamerà AInterface :: get_r o A1 :: get_r al momento della compilazione - deve sapere quale valore di ritorno otterrà, quindi insiste su entrambi i metodi restituendo lo stesso tipo. Questo fa parte della specifica C ++.

Per risolvere il problema, se A1 :: get_r restituisce un puntatore a RetInterface, i metodi virtuali in RetInterface continueranno a funzionare come previsto e l'oggetto corretto verrà eliminato quando il puntatore viene distrutto. Non sono necessari diversi tipi di restituzione.

forse potresti usare un parametro out per aggirare " covarianza con boost restituito shared_ptrs.

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

poiché sospetto che un chiamante possa inserire un tipo shared_ptr più raffinato come argomento.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top