modelos covariantes C ++
-
11-07-2019 - |
Pergunta
Eu me sinto como este tem sido solicitado antes, mas eu sou incapaz de encontrá-lo no SO, nem posso encontrar qualquer coisa útil no Google. Talvez "covariante" não é a palavra que eu estou procurando, mas este conceito é muito semelhante aos tipos de retorno covariant em funções, então eu acho que é provavelmente correto. Aqui está o que eu quero fazer e dá-me um erro do compilador:
class Base;
class Derived : public Base;
SmartPtr<Derived> d = new Derived;
SmartPtr<Base> b = d; // compiler error
Suponha que essas classes são totalmente concretizados ... Eu acho que você começa a idéia. Ele não pode converter um SmartPtr<Derived>
em um SmartPtr<Base>
por algum motivo obscuro. Lembro-me que isso é normal em C ++ e muitas outras línguas, embora no momento eu não consigo lembrar o porquê.
A minha pergunta raiz é: qual é a melhor maneira de executar esta operação de atribuição? Atualmente, eu estou puxando o ponteiro fora do SmartPtr
, fazendo um upcast-lo explicitamente no tipo base, em seguida, envolvê-lo em uma nova SmartPtr
do tipo apropriado (note que este não está vazando recursos porque nossa classe SmartPtr
home-grown usa referência intrusiva contagem). Isso é longo e confuso, especialmente quando eu então necessidade de envolver o SmartPtr
em outro objeto ... todos os atalhos?
Solução
Tanto o construtor de cópia e operador de atribuição deve ser capaz de dar uma Smartptr de um tipo diferente e tentar copiar o ponteiro de um para o outro. Se os tipos não são compatíveis, o compilador vai reclamar, e se eles são compatíveis, você resolveu seu problema. Algo parecido com isto:
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
};
Outras dicas
SmartPtr<Base>
e SmartPtr<Derived>
são duas instâncias distintas de um modelo do SmartPtr
. Estas novas classes não compartilham a herança que Base
e Derived
fazer. Daí, o seu problema.
qual é a melhor maneira de executar esta operação de atribuição?
SmartPtr<Base> b = d;
não invoca operador de atribuição. Isso chama a cópia-ctor (a cópia é elidido na maioria dos casos) e é exatamente como se você escreveu:
SmartPtr<Base> b(d);
Fornecer uma cópia-ctor que leva um SmartPtr<OtherType>
e implementá-lo. O mesmo vale para o operador de atribuição. Você terá que escrever o copy-ctor e op = tendo em mente a semântica de Smartptr.
Os modelos não são covariant, e isso é bom; Imagine o que aconteceria no caso seguinte:
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!
Para conseguir o que você está tentando fazer, a classe ponteiro inteligente deve ter um construtor templatized, que leva qualquer outro ponteiro inteligente ou um ponteiro de outro tipo. Você poderia ter um olhar para boost :: shared_ptr, que faz exatamente isso.
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)
};
Depende da classe SmartPtr
. Se ele tem um construtor de cópia (ou no seu caso, operador de atribuição) que leva SmartPtr<T>
, onde T é o tipo que foi construído com, então ele não está indo para o trabalho, porque SmartPtr<T1>
não está relacionado com SmartPtr<T2>
mesmo que T1 e T2 são relacionados por herança.
No entanto, se Smartptr tem um templatized cópia construtor / operador de atribuição, com parâmetro do modelo TOther
, que aceita SmartPtr<TOther>
, então ele deve funcionar.
Supondo que você tenha o controle da classe Smartptr, a solução é fornecer um construtor templated:
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 o T e S tipos são compatíveis, ele vai trabalhar, se eles não são você terá um erro de compilação.
Eu acho que a coisa mais fácil é fornecer conversão automática para outro Smartptr de acordo com o seguinte:
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;
};
Note que esta implementação é robusta no sentido de que o modelo de operador de conversão não precisa de saber sobre a semântica do ponteiro inteligente, de modo a contagem de referência não precisa replicado etc.