Pergunta

Considere o seguinte código de exemplo:

class Foo
{
};

class Bar : public Foo
{
};

class FooCollection
{
protected:
    vector<shared_ptr<Foo> > d_foos;
};

class BarCollection : public FooCollection
{
public:
    vector<shared_ptr<Bar> > &getBars()
    {
        // return d_foos won't do here...
    }
};

Tenho um problema como esse no meu projeto atual. O código do cliente usa BarCollection, que armazena ponteiros para Bars dentro d_foos que é declarado em FooCollection. Agora gostaria de expor a coleta de ponteiros a barras ao código do cliente. Eu poderia apenas dar acesso ao código do cliente ao vetor de ponteiros para Foos e os lançam para os ponteiros para Bars no código do cliente, mas isso parece errado, pois o cliente não precisa saber sobre Fooexistência.

Eu também poderia definir um get() membro que recupera objetos de d_foos E os lança, mas isso parece bastante desajeitado. De preferência, eu gostaria de apenas devolver D_foos como um vector<shared_ptr<Bar> > &, mas não consigo fazer isso.

Também pode ser que meu design esteja simplesmente errado. Parecia para a solução mais natural, como Bar é uma especialização de Foo e BarCollection é uma especialização de FooCollection E eles compartilham funcionalidade.

Você poderia sugerir boas soluções para implementar getBars dentro BarCollection Ou melhor alternativas de design?

Editar:

Acontece que meu design foi realmente ruim. O barcollection não é uma foocollection, apesar de exigir toda a funcionalidade da FOOCLEÇÃO. Minha solução atual com base nas respostas abaixo - que é muito mais limpa - é agora:

class Foo
{
};

class Bar : public Foo
{
};

template<class T>
class Collection
{
    vector<shared_ptr<T> > d_items;
};

typedef Collection<Foo> FooCollection;

class BarCollection : public Collection<Bar>
{
    // Additional stuff here.
};

Obrigado por todas as excelentes sugestões e exemplos!

Foi útil?

Solução

template<class T>
class MyContainer {
  vector<shared_ptr<T> > d_foos;
public:
  vector<shared_ptr<T> > & getVector();
};

class FooCollection : public MyContainer<Foo> {
};

class BarCollection : public MyContainer<Bar> {
};

Outras dicas

Sugiro expor os iteradores de suas classes de contêineres, em vez do contêiner membro. Dessa forma, não importa qual é o tipo de contêiner.

O problema é que você está tentando misturar e combinar dois tipos diferentes de polimorfismo, de uma maneira que não funcionará. O polimorfismo de modelos com tempo de compilação, seguro-tipo, não permite que você substitua um tipo base por um tipo derivado. O sistema de modelo de C ++ não faz associação entre

class<Foo>

e

class<Bar>

Uma sugestão pode ser criar um adaptador derivado de Foo que seria lançado na classe correta:

 template <class derived, class base>
 class DowncastContainerAdapter
 {
 private:
     std::vector< boost::shared_ptr<base> >::iterator curr;
     std::vector< boost::shared_ptr<base> >::const_iterator end;
 public:
     DowncastContainerAdapter(/*setup curr & end iterators*/)
     {
         // assert derived actually is derived from base
     }

     boost::shared_ptr<derived> GetNext()
     {
         // increment iterator
         ++curr;
         return dynamic_cast<base>(*curr);
     }

     bool IsEnd()
     {
         return (curr == end);
     }
 };

Nota Esta classe terá o mesmo problema que um iterador, operação no vetor pode invalidar esta classe.

Outro pensamento

Você pode não perceber, mas pode ser perfeitamente bom apenas devolver um vetor de foo. O usuário do bar já deve ter pleno conhecimento do Foo, pois, ao incluir o bar.h, eles devem obter o foo.h através do bar.h. A razão é que, para o bar herdar de Foo, ele deve ter pleno conhecimento da classe via foo.h. Eu sugeriria em vez de usar a solução acima, se for possível, faça do Foo (ou de uma super classe de Foo) uma classe de interface e passar por torno de vetores de ponteiros para essa classe de interface. Este é um padrão bastante comum e não levantará as sobrancelhas que essa solução instável que eu criei com o poder :). Então, novamente, você pode ter seus motivos. Boa sorte de qualquer maneira.

A questão é: por que você faria isso? Se você der ao usuário uma coleção de ponteiros para barrar, você assume que há apenas barras nele; portanto, armazenar internamente os ponteiros em uma coleção para foo não faz sentido. Se você armazenar diferentes subtipos de foo em sua coleção de ponteiro para foo, não poderá devolvê -lo como uma coleção de indicadores para barra, pois nem todos os objetos existem barras. No primeiro caso, (você sabe que tem apenas barras), você deve usar uma abordagem modelada, conforme sugerido acima. Caso contrário, você precisa repensar, o que você realmente quer.

Você não pode substituir isso por uma coleção templatizada em foo / bar?, Algo assim

class Collection<T> {
protected:
    vector<shared_ptr<T> > d_foos;
};

typedef Collection<Foo> FooCollection;
typedef Collection<Bar> BarCollection;

Você tem uma necessidade especial de ter BarCollection derivado de FooCollection? Porque geralmente a BarCollection não é uma FooCollection, geralmente muitas das coisas que podem ser feitas com um FooCollection não deve ser feito com um BarCollection. Por exemplo:

BarCollection *bc = new BarCollection();
FooCollection *fc = bc; // They are derived from each other to be able to do this
fc->addFoo(Foo());      // Of course we can add a Foo to a FooCollection

Agora nós adicionamos um Foo objetar ao que deveria ser um BarCollection. Se o BarCollection tenta acessar este elemento recém -adicionado e espera que seja um Bar, todos os tipos de coisas feias vão acontecer.

Normalmente, você deseja evitar isso e não tem suas aulas de coleção derivadas uma da outra. Veja também perguntas cerca de recipientes de fundição de tipos derivados para obter mais respostas sobre este tópico ...

Primeiro de tudo, vamos falar sobre shared_ptr. Você sabe sobre: boost::detail::dynamic_cast_tag ?

shared_ptr<Foo> fooPtr(new Bar());
shared_ptr<Bar> barPtr(fooPtr, boost::detail::dynamic_cast_tag());

Esta é uma maneira muito útil. Sob a capa, apenas executa um dynamic_cast, nada sofisticado lá, exceto uma notação mais fácil. O contrato é o mesmo que o clássico: se o objeto apontou para não ser realmente é um Bar (ou derivado dele), então você obtém um ponteiro nulo.

Voltar à sua pergunta: código ruim.

BarCollection não é um FooCollection, como mencionado, você está, portanto, com problemas porque pode apresentar outros elementos no vetor de ponteiros que Bar uns.

Não vou me estender sobre isso, porque isso está além da pergunta em questão, e acho que nós (como aqueles que tentamos responder) devemos nos impedir disso.

Você não pode passar uma referência, mas pode passar um View.

Basicamente, a View é um novo objeto que atua como um Proxy para o antigo. É relativamente fácil usando Boost.iterators do exemplo.

class VectorView
{
  typedef std::vector< std::shared_ptr<Foo> > base_type;

public:
  typedef Bar value_type;
  // all the cluttering

  class iterator: boost::iterator::iterator_adaptor<
    iterator,
    typename base_type::iterator,
    std::shared_ptr<Bar>
  >
  {
    typename iterator_adaptor::reference dereference() const
    {
      // If you have a heart weakness, you'd better stop here...
      return reinterpret_cast< std::shared_ptr<Bar> >(this->base_reference());
    }
  };

  // idem for const_iterator

  // On to the method forwarding
  iterator begin() { return iterator(m_reference.begin()); }

private:
  base_type& m_reference;
}; // class VectorView

O verdadeiro problema aqui é obviamente o reference pedaço. Obtendo um NEW shared_ptr objeto é fácil e permitir executar um dynamic_cast como requerido. Obtendo um reference para o ORIGINAL shared_ptr mas interpretado como o tipo necessário ... realmente não é o que eu gosto de ver no código.

Observação:
Pode haver uma maneira de fazer melhor do que isso usando boost.fusion transform_view classe, mas eu não consegui descobrir.

Em particular, usando transform_view eu posso conseguir shared_ptr<Bar> Mas eu não posso conseguir um shared_ptr<Bar>& Quando eu desreferi meu iterador, o que é irritante, dado que o único uso de retornar uma referência ao subjacente vector (e não um const_reference) é realmente modificar a estrutura do vector e os objetos que ele contém.

Nota 2:
Por favor, considere refatorar. Houve excelentes sugestões lá.

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