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?

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top