2

複数のタイプのオブジェクトのコレクションを持つためのひどくない方法はありますか? 共通のベースから各タイプを導き出すことができて、私は完全に満足しています。コレクションをコピーしたり、割り当てたりできるように、適切なセマンティクスが必要です。

明らかに、基本クラスのベクトルまたはリストを使用することはできません。オブジェクトはスライスされ、コピーはまったく機能しません。ベクトルやポインターのリスト、またはスマート ポインターの使用は機能しますが、適切なコピー セマンティクスは得られません。

正常なコピー セマンティクスを取得するには、Boost のようなものを使用する必要がありますptr_vector。しかし、これには、面倒でエラーが発生しやすいインフラストラクチャが必要です。基本的に、基本クラスから新しいクラスを派生させることはできません。これは、それがコレクションに入ると、適切にコピーされないためです。

これは非常に一般的なことのように思えますが、私が知っている解決策はすべてひどいものです。C++ には、特定のインスタンスと同一のオブジェクトの新しいインスタンスを作成する方法が根本的に欠けているようです-その型にコピーコンストラクターがある場合でも。cloneorduplicate関数を作成するには、すべての派生クラスで慎重にオーバーロードする必要があります。ベースから派生した新しいクラス (またはそのベースから派生した他のクラス) を作成するときにそれを怠ると、コレクションが壊れます。

本当に良い方法はありませんか?

4

2 に答える 2

3

std::vector<boost::any>私が思うに、これのほとんどを行うために使用できます。

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

出力が得られます:

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

これは私が期待するものです。

編集:

メンバーを共通の基本型として扱うことができるという要件に沿って、 に非常に似たものが必要になります。boost::anyこれはありがたいことに、比較的単純なクラスです。

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

これで、次のことができるはずです。

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

私は実際には any_cast の構文が嫌いで、この機会を利用して「as」メンバー関数を追加します..最後の行を次のように記述できるようにします。

v[0].as<DerivedA>()->only_in_a();
于 2012-05-19T07:25:13.707 に答える
0

さて、私のコメントをフォローアップするために、boost :: anyを使用せずにこれを行う方法があります。ほとんどの場合、優れたパフォーマンスを提供するはずですが、確かにもう少し複雑です。私のソリューションは、2つのアイデアを組み合わせています。コンテンツのタイプを排除するホルダークラスの使用と、軽量のカスタムRTTIです。ホルダークラスに意味のあるコピーと割り当てのセマンティクスを提供し、ホルダーのコンテナーを使用してオブジェクトのコレクションを管理します。軽量RTTIを使用して、必要に応じてオブジェクトの実際のタイプを検出します。これが私が提案していることを示すためのいくつかのコードです:

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

そして、これが出力です:

derived1
derived2
于 2012-05-19T16:34:15.930 に答える