Modelli covarianti C ++
-
11-07-2019 - |
Domanda
Sento che questo è stato chiesto prima, ma non riesco a trovarlo su SO, né posso trovare nulla di utile su Google. Forse & Quot; covariant & Quot; non è la parola che sto cercando, ma questo concetto è molto simile ai tipi di ritorno covarianti sulle funzioni, quindi penso che sia probabilmente corretto. Ecco cosa voglio fare e mi dà un errore del compilatore:
class Base;
class Derived : public Base;
SmartPtr<Derived> d = new Derived;
SmartPtr<Base> b = d; // compiler error
Supponi che quelle classi siano completamente arricchite ... Penso che tu abbia avuto l'idea. Non può convertire un SmartPtr<Derived>
in SmartPtr<Base>
per qualche motivo poco chiaro. Ricordo che questo è normale in C ++ e in molte altre lingue, anche se al momento non ricordo il perché.
La mia domanda principale è: qual è il modo migliore per eseguire questa operazione di assegnazione? Attualmente sto estraendo il puntatore dal SmartPtr
, eseguendo l'upgrade esplicito al tipo base, quindi avvolgendolo in un nuovo <=> del tipo appropriato (si noti che questo non sta perdendo risorse perché il nostro < => classe utilizza il conteggio di riferimenti intrusivi). È lungo e disordinato, soprattutto quando devo quindi avvolgere <=> in un altro oggetto ... qualche scorciatoia?
Soluzione
Sia il costruttore della copia che l'operatore di assegnazione dovrebbero essere in grado di prendere uno SmartPtr di un tipo diverso e tentare di copiare il puntatore dall'uno all'altro. Se i tipi non sono compatibili, il compilatore si lamenterà e se sono compatibili, hai risolto il problema. Qualcosa del genere:
template<class Type> class SmartPtr
{
....
template<class OtherType> SmartPtr(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> copy constructor
template<class OtherType> SmartPtr<Type> &operator=(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> assignment operator
};
Altri suggerimenti
SmartPtr<Base>
e SmartPtr<Derived>
sono due istanze distinte di un modello SmartPtr
. Queste nuove classi non condividono l'eredità che fanno Base
e Derived
. Quindi, il tuo problema.
qual è il modo migliore per eseguire questa operazione di assegnazione?
SmartPtr<Base> b = d;
Non invoca l'operatore di assegnazione. Questo invoca il copy-ctor (la copia è elisa nella maggior parte dei casi) ed è esattamente come se tu scrivessi:
SmartPtr<Base> b(d);
Fornire un copia-ctor che accetta un SmartPtr<OtherType>
e lo implementa. Lo stesso vale per l'operatore di assegnazione. Dovrai scrivere il copy-ctor e op = tenendo presente la semantica di SmartPtr.
I modelli non sono covarianti, e questo va bene; immagina cosa accadrebbe nel caso seguente:
vector<Apple*> va;
va.push_back(new Apple);
// Now, if templates were covariants, a vector<Apple*> could be
// cast to a vector<Fruit*>
vector<Fruit*> & vf = va;
vf.push_back(new Orange); // Bam, we just added an Orange among the Apples!
Per ottenere ciò che stai cercando di fare, la classe SmartPointer deve avere un costruttore templatized, che accetta un altro SmartPointer o un puntatore di un altro tipo. Puoi dare un'occhiata a boost :: shared_ptr, che fa esattamente questo.
template <typename T>
class SmartPointer {
T * ptr;
public:
SmartPointer(T * p) : ptr(p) {}
SmartPointer(const SmartPointer & sp) : ptr(sp.ptr) {}
template <typename U>
SmartPointer(U * p) : ptr(p) {}
template <typename U>
SmartPointer(const SmartPointer<U> & sp) : ptr(sp.ptr) {}
// Do the same for operator= (even though it's not used in your example)
};
Dipende dalla classe SmartPtr
. Se ha un costruttore di copie (o nel tuo caso, operatore di assegnazione) che accetta SmartPtr<T>
, dove T è il tipo con cui è stato costruito, allora non funzionerà, poiché SmartPtr<T1>
non è correlato a SmartPtr<T2>
anche se T1 e T2 sono correlati per eredità.
Tuttavia, se SmartPtr ha un templatized costruttore di copia / operatore di assegnazione, con il parametro modello TOther
, che accetta SmartPtr<TOther>
, allora dovrebbe funzionare.
Supponendo che tu abbia il controllo della classe SmartPtr, la soluzione è fornire un costruttore basato su modelli:
template <class T>
class SmartPtr
{
T *ptr;
public:
// Note that this IS NOT a copy constructor, just another constructor that takes
// a similar looking class.
template <class O>
SmartPtr(const SmartPtr<O> &src)
{
ptr = src.GetPtr();
}
// And likewise with assignment operator.
};
Se i tipi T e O sono compatibili, funzionerà, in caso contrario verrà visualizzato un errore di compilazione.
Penso che la cosa più semplice sia fornire la conversione automatica in un altro SmartPtr in base a quanto segue:
template <class T>
class SmartPtr
{
public:
SmartPtr(T *ptr) { t = ptr; }
operator T * () const { return t; }
template <class Q> operator SmartPtr<Q> () const
{ return SmartPtr<Q>(static_cast<Q *>(static_cast<T *>(* this))); }
private:
T *t;
};
Nota che questa implementazione è solida nel senso che il modello dell'operatore di conversione non ha bisogno di conoscere la semantica del puntatore intelligente, quindi il conteggio dei riferimenti non deve essere replicato ecc.