質問
複数のタイプのオブジェクトのコレクションを持つべきでない方法はありませんか?私は共通の基地から各タイプを導き出すことを完全に満足させています。コレクションをコピー、割り当て、など、コレクションをコピーすることができるように賢明な意味論が必要です。
明らかに、基本クラスのベクトルやリストを使用することはできません。オブジェクトがスライスされ、コピーはまったく機能しません。ベクトルやポインタまたはスマートポインタのリストを使用するのは機能しますが、SANEコピーセマンティクスを取得しません。
SANEコピーセマンティクスを取得するには、Boostのptr_vector
のようなものを使用する必要があります。しかし、これには痛みを伴うエラーが発生しやすいインフラストラクチャが必要です。基本的に、それがコレクションに入ったら、それは正しくコピーされないので、基本クラスから新しいクラスを導出することはできません。
これは、私が知っているすべての解決策がとてもひどいものであるような一般的なことのようです。 C ++は、そのタイプがコピーコンストラクタを持つ場合でも、C ++は、特定のインスタンスと同じオブジェクトの新しいインスタンスを作成する方法が不足しています。そしてclone
またはduplicate
関数を作成するには、派生クラスごとに慎重な過負荷が必要です。基本から派生した新しいクラス(またはそのベースから派生した他のクラス)を作成したときに実行できなかった場合 - ブーム、コレクションは壊れます。
本当に良くない方法はありませんか?
解決
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();
. 他のヒント
大丈夫、私のコメントをフォローアップするために、Boost ::を使用せずにこれを行う方法があります。私の解決策は、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
.