Pergunta

Eu tenho um código como este:

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

Este código não compila.

No visual studio levanta

C2555: substituindo tipo diferente de retorno de função virtual e não é covariant

Se eu não usar boost::shared_ptr mas retornam ponteiros crus, as compilações de código (eu entendo que isso é devido a tipos de retorno covariantes em C ++). Eu posso ver o problema é porque boost::shared_ptr de Ret1 não é derivado de boost::shared_ptr de RetInterface. Mas eu quero voltar boost::shared_ptr de Ret1 para uso em outras classes, senão eu deve converter o valor devolvido após o retorno.

  1. Estou fazendo algo errado?
  2. Se não, por que é a linguagem como este - ele deve ser extensível para lidar com a conversão entre ponteiros inteligentes neste cenário? Existe uma solução desejável?
Foi útil?

Solução

Em primeiro lugar, esta é realmente como ele funciona em C ++: o tipo de retorno de uma função virtual em uma classe derivada deve ser o mesmo que na classe base. Não é a exceção especial que uma função que retorna uma referência / ponteiro para alguma classe X pode ser substituído por uma função que retorna uma referência / ponteiro para uma classe que deriva de X, mas como você note que este não permite inteligente ponteiros (como shared_ptr), apenas para ponteiros simples.

Se o seu RetInterface interface é suficientemente abrangente, então você não precisa saber o tipo retornado real no código de chamada. Em geral, não faz sentido qualquer maneira: o get_r razão é uma função virtual em primeiro lugar é porque você estará chamando-o através de um ponteiro ou referência ao AInterface classe base, caso em que você não pode saber o que digite o classe derivada voltaria. Se você está chamando isso com uma referência A1 real, você pode simplesmente criar uma função get_r1 separado no A1 que faz o que você precisa.

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

Como alternativa, você pode usar o padrão do visitante ou algo como técnica meu dupla dinâmica Despacho para passar uma chamada de retorno para o objeto retornado que pode invocar o callback com o tipo correto.

Outras dicas

E sobre esta solução:

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

por exemplo:

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

Não é possível alterar tipos de retorno (para não-ponteiro, tipos de retorno não-referência), quando a sobrecarga de métodos em C ++. A1::get_r deve retornar um boost::shared_ptr<RetInterface>.

Anthony Williams tem um bom abrangente resposta .

Aqui é a minha tentativa:

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 um pequeno exemplo:

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

Explicações:

Esta solução implica meta-programação e modificar as classes usadas no ponteiros inteligentes covariantes.

O simples Child template struct está aqui para vincular o Parent tipo e herança. Qualquer classe herdada de Child<T> vai herdar de T e definir T como Parent. As classes utilizadas na ponteiros inteligentes covariantes precisa deste tipo a ser definido.

O has_parent classe é usada para detectar em tempo de compilação se uma classe define o tipo Parent ou não. Esta parte não é minha, eu usei o mesmo código para detectar se existe um método ( veja aqui )

Como queremos covariância com ponteiros inteligentes, queremos que nossos ponteiros inteligentes para imitar a arquitetura classe existente. É mais fácil explicar como ele funciona no exemplo.

Quando um CovariantSharedPtr<B> é definido, ele herda de ICovariantSharedPtr<B>, que é interpretado como ICovariantSharedPtr<B, has_parent<B>::value>. Como herda B de Child<A>, has_parent<B>::value é verdade, então ICovariantSharedPtr<B> é ICovariantSharedPtr<B, true> e herda de ICovariantSharedPtr<B::Parent> que é ICovariantSharedPtr<A>. Como A não tem Parent definido, has_parent<A>::value é falso, ICovariantSharedPtr<A> é ICovariantSharedPtr<A, false> e herda a partir do nada.

O ponto principal é como Binherits de A, temos ICovariantSharedPtr<B>inheriting de ICovariantSharedPtr<A>. Assim, qualquer método retornando um ponteiro ou uma referência em ICovariantSharedPtr<A> pode ser sobrecarregado por um método retornar o mesmo em ICovariantSharedPtr<B>.

Existe uma solução elegante postou em este post (de Raoul Borges)

Um trecho do pouco antes de adicionar suporte para herança mulitple e métodos abstratos é:

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

Gostaria de incentivar a leitura do artigo completo. Seu simplesmente escrito e bem explicado.

Sr. Fooz respondeu a parte 1 da sua pergunta. Parte 2, ele funciona dessa maneira porque o compilador não sei se ele vai estar chamando AInterface :: get_r ou A1 :: get_r em tempo de compilação - ele precisa saber o valor de retorno que vai para chegar, por isso insiste em ambos os métodos devolver o mesmo tipo. Esta é parte da especificação C ++.

Para a solução alternativa, se A1 :: get_r retorna um ponteiro para RetInterface, os métodos virtuais em RetInterface irá ainda trabalho como esperado, e o próprio objeto será excluído quando o ponteiro é destruído. Não há necessidade de diferentes tipos de retorno.

talvez você poderia usar um parâmetro de saída para contornar "covariância com devolvidos shared_ptrs impulso.

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

desde que eu suspeito que um chamador pode cair em um tipo de shared_ptr mais refinado como argumento.

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