Pergunta

Existe alguma não-terrível maneira de ter uma coleção de objetos de mais de um tipo?Eu estou perfeitamente feliz para derivar cada tipo de partir de uma base comum.Eu preciso sensível semântica para a coleta pode ser copiado, atribuído, e assim por diante.

Obviamente, eu não posso usar apenas um vetor ou lista da classe base.Os objetos serão cortados e copiando não funciona em todos.Usando vetores ou listas de ponteiros ou apontadores inteligentes funciona, mas você não sane cópia semântica.

Para obter sã cópia semântica, você precisa usar algo como Impulso do ptr_vector.Mas isso requer um doloroso e sujeito a erros infra-estrutura.Essencialmente, você não pode simplesmente derivar uma nova classe, a classe base, porque se ele já vai para sua coleção, ele não será copiada corretamente.

Isso parece como uma coisa comum de se fazer e de todas as soluções que eu conheço são tão terrível.Parece que o C++ é, fundamentalmente, falta uma forma de criar uma nova instância de um objeto idêntico ao de uma dada instância -- mesmo que o tipo tem um construtor de cópia.E fazendo uma clone ou duplicate a função requer o cuidado de sobrecarga em cada classe derivada.Se você falhar em fazê-lo ao criar uma nova classe derivada de base (ou qualquer outra classe derivada a partir de que base) -- pronto, sua coleção de quebras.

Há realmente nenhuma maneira melhor?

Foi útil?

Solução

Você pode usar std::vector<boost::any> fazer mais isso eu acho.

#include "boost/any.hpp"
#include <vector>
#include <iostream>

//Simple class so we can see what's going on
class MM {
  public:
    MM()               { std::cout<<"Create @ "<<this<<std::endl; }
    MM( const MM & o ) { std::cout<<"Copy "<<&o << " -> "<<this<<std::endl; }
    ~MM()              { std::cout<<"Destroy @ "<<this<<std::endl; }
};

int main()
{
  //Fill a vector with some stuff
  std::vector<boost::any> v;
  v.push_back(0);
  v.push_back(0);
  v.push_back(0);
  v.push_back(0);

  //Overwrite one entry with one of our objects.
  v[0] = MM();

  std::cout<<"Copying the vector"<<std::endl;
  std::vector<boost::any> w;
  w = v;

  std::cout<<"Done"<<std::endl;
}

Para que eu obter o resultado:

Create @ 0xbffff6ae
Copy 0xbffff6ae -> 0x100154
Destroy @ 0xbffff6ae
Copying the vector
Copy 0x100154 -> 0x100194
Done
Destroy @ 0x100194
Destroy @ 0x100154

Que é o que eu esperava ver.

EDITAR:

Em linha com os seus requisitos para ser capaz de tratar os membros como algumas de base comum-tipo, você vai precisar de algo muito semelhante ao boost::any, que, felizmente, é relativamente simples de classe.

template<typename BASE>
class any_with_base
{
    // ... Members as for boost::any

    class placeholder
    {
        virtual BASE * as_base() = 0;

        //Other members as in boost::any::placeholder
    };

    template<typename ValueType>
    class holder : public placeholder
    {
        virtual BASE * as_base() { return (BASE*)&held; }
        //Other members as in boost::any::holder<T>
    };

    BASE* as_base() { return content?content->as_base():0; }
}

Agora você deve ser capaz de fazer isso:

vector< any_with_base<Base> > v;
v.push_back( DerivedA() );
v.push_back( DerivedB() );

v[0].as_base()->base_fn();
v[1].as_base()->base_fn();

any_cast<DerivedA>(v[0])->only_in_a();

Na verdade, eu não gosto da sintaxe de any_cast e gostaria de usar esta oportunidade para adicionar um "como" membro da função.para que eu pudesse escrever a última linha como:

v[0].as<DerivedA>()->only_in_a();

Outras dicas

Tudo bem, a acompanhar o meu comentário, há uma maneira de fazer isso sem usar o boost::qualquer que deve oferecer um desempenho superior na maioria dos casos, mas é reconhecidamente um pouco mais envolvido.A minha solução combina duas idéias:o uso de uma classe de titular que elides o tipo de seu conteúdo, e leve personalizado RTTI.Nós damos a classe de titular significativo de cópia e de atribuição de semântica e nós recipientes de uso de suportes para gerenciar uma coleção de objetos.Usamos a nossa leve RTTI para descobrir o verdadeiro tipos de objetos, quando necessário.Aqui está um código para demonstrar o que eu estou propondo:

#include <vector>
#include <cassert>
#include <iostream>

#include <boost/cast.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/static_assert.hpp>

/// This template makes it possible to enforce the invariant that every type in a
/// hierarchy defines the id( ) function, which is necessary for our RTTI.  Using
/// a static assertion, we can force a compile error if a type doesn't provide id( ).

template< typename T >
struct provides_id {
  typedef char one;
  typedef long two;

  template< typename U, std::string const &(U::*)( ) const = &U::id >
  struct id_detector { };

  template< typename U > static one test( id_detector< U > * );
  template< typename U > static two test( ... );

  enum { value = sizeof(test<T>(0)) == sizeof(one) };
};

/// Base class for the holder.  It elides the true type of the object that it holds,
/// providing access only through the base class interface.  Since there is only one
/// derived type, there is no risk of forgetting to define the clone() function.

template< typename T >
struct holder_impl_base {
  virtual ~holder_impl_base( ) { }

  virtual T       *as_base( )       = 0;
  virtual T const *as_base( ) const = 0;

  virtual holder_impl_base *clone( ) const = 0;
};

/// The one and only implementation of the holder_impl_base interface.  It stores
/// a derived type instance and provides access to it through the base class interface.
/// Note the use of static assert to force the derived type to define the id( )
/// function that we use to recover the instance's true type.

template< typename T, typename U >
struct holder_impl : public holder_impl_base< T > {
  BOOST_STATIC_ASSERT(( provides_id< U >::value ));

  holder_impl( U const &p_data )
    : m_data( p_data )
  { }

  virtual holder_impl *clone( ) const {
    return new holder_impl( *this );
  }

  virtual T *as_base( ) {
    return &m_data;
  }

  virtual T const *as_base( ) const {
    return &m_data;
  }

private:

  U m_data;
};

/// The holder that we actually use in our code.  It can be constructed from an instance
/// of any type that derives from T and it uses a holder_impl to elide the type of the
/// instance.  It provides meaningful copy and assignment semantics that we are looking
/// for.

template< typename T >
struct holder {

  template< typename U >
  holder( U const &p_data )
    : m_impl( new holder_impl< T, U >( p_data ))
  { }

  holder( holder const &p_other )
    : m_impl( p_other.m_impl -> clone( ))
  { }

  template< typename U >
  holder &operator = ( U const &p_data ) {
    m_impl.reset( new holder_impl< T, U >( p_data ));
    return *this;
  }

  holder &operator = ( holder const &p_other ) {
    if( this != &p_other ) {
      m_impl.reset( p_other.m_impl -> clone( ));
    }

    return *this;
  }

  T *as_base( ) {
    return m_impl -> as_base( );
  }

  T const *as_base( ) const {
    return m_impl -> as_base( );
  }

  /// The next two functions are what we use to cast elements to their "true" types.
  /// They use our custom RTTI (which is guaranteed to be defined due to our static
  /// assertion) to check if the "true" type of the object in a holder is the same as
  /// as the template argument.  If so, they return a pointer to the object; otherwise
  /// they return NULL.

  template< typename U >
  U *as( ) {
    T *base = as_base( );

    if( base -> id( ) == U::static_id( )) {
      return boost::polymorphic_downcast< U * >( base );
    }

    return 0;
  }

  template< typename U >
  U const *as( ) const {
    T *base = as_base( );

    if( base -> id( ) == U::static_id( )) {
      return boost::polymorphic_downcast< U const * >( base );
    }

    return 0;
  }

private:

  boost::scoped_ptr< holder_impl_base< T > > m_impl;
};

/// A base type and a couple derived types to demonstrate the technique.

struct base {
  virtual ~base( )
  { }

  virtual std::string const &id( ) const = 0;
};

struct derived1 : public base { 
  std::string const &id( ) const {
    return c_id;
  }

  static std::string const &static_id( ) {
    return c_id;
  }

private:

  static std::string const c_id;
};

std::string const derived1::c_id( "derived1" );

struct derived2 : public base {
  std::string const &id( ) const {
    return c_id;
  }

  static std::string const &static_id( ) {
    return c_id;
  }

private:

  static std::string const c_id;
};

std::string const derived2::c_id( "derived2" );

/// A program to demonstrate that the technique works as advertised.

int main( ) {
  std::vector< holder< base > > vector1;

  vector1.push_back( derived1( ));
  vector1.push_back( derived2( ));

  std::vector< holder< base > > vector2 = vector1;

  /// We see that we have true copies!

  assert( vector1[0].as_base( ) != vector2[0].as_base( ));
  assert( vector1[1].as_base( ) != vector2[0].as_base( ));

  /// Easy assignment of container elements to new instances!

  vector2[0] = derived2( );
  vector2[1] = derived1( );

  // Recovery of the "true" types!

  std::vector< holder< base > >::iterator l_itr = vector1.begin( );
  std::vector< holder< base > >::iterator l_end = vector1.end  ( );

  for( ; l_itr != l_end; ++l_itr ) {
    if( derived1 *ptr = l_itr -> as< derived1 >( )) {
      std::cout << ptr -> static_id( ) << std::endl;
    }
    else if( derived2 *ptr = l_itr -> as< derived2 >( )) {
      std::cout << ptr -> static_id( ) << std::endl;
    }
  }
}

E aqui está o resultado:

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