Умные указатели как члены класса для полиморфизма - PullRequest
4 голосов
/ 09 ноября 2011

Я новичок в умных указателях и буду очень признателен, если кто-нибудь подскажет мне, правильно ли то, как я работаю со умными указателями в качестве учеников.Точнее говоря, решение, которого я хотел бы достичь, заключается в контексте полиморфизма классов и должно быть идеально безопасным для исключений.

Учитывая контейнер разнородных объектов (std::vector<shared_ptr<CBase> > my_vector),обычный способ добавления элементов: my_vector.push_back( shared_ptr<CBase>(new CChild(1))), так что позже можно вызвать функцию-член определенного производного класса, выполнив: my_vector[0]->doSomething().

  1. Что бы я хотелКак достичь, это добавить объекты стека к вектору и все еще иметь возможность делать полиморфизм.Интуитивнокак: CChild<float> obj1(1); my_vector.push_back(obj1).Чтобы решить эту проблему, я сейчас использую Virtual Constructor Idiom : CChild obj1(1); my_vector.push_back(obj1.clone());.
    Обратите внимание, что в some моих производных классов есть статические функции-члены, которые создают объекты, например:CChild<float> obj1 = CChild<float>::initType2(1);

  2. Из-за проблем с требованиями, а также из-за чистого интерфейса у меня теперь есть новый класс CFoo<T>, в котором в качестве члена данных используется интеллектуальный указатель на CBase<T>class.
    Идея состоит в том, что помимо того, что он содержит другие новые закрытые члены, этот класс инкапсулирует / обрабатывает интеллектуальные указатели на производные объекты, так что мне разрешено делать sth.как:
    CFoo<float> myfoo(CChild<float>::initType2(1)); my_vector.push_back(myfoo);.Это означает, что контейнер теперь имеет тип vector<CFoo<T> > вместо типа vector<shared_ptr<CBase> >

Именно в этом контексте я хотел бы знать, как реализовать конструкторы для класса сsmart pointers как ученики?Как насчет реализации operator = после идиомы копирования-обмена?Ниже я приведу некоторые иллюстрации дизайна моего класса:

template < typename T >
class CBase{
    public:
        CBase(){};
        virtual ~CBase(){};
        ...
        virtual CBase<T> * clone() const = 0;
        virtual CBase<T> * create() const = 0;
};

template < typename T >
class CChild1 : public CBase{
    public:
        ...
        CChild1<T> * clone() const  { return new CChild1<T>(*this); }
        CChild1<T> * create() const { return new CChild1<T>(); }
        static CChild1 initType1(double, double);
        static CChild1 initType2(int);

};

template < typename T >
struct type{
    typedef std::tr1::shared_ptr<T> shared_ptr;
};

template < typename T >
class CFoo{

    public:

        CFoo();
        CFoo( const CBase<T> &, int = 0 );
        CFoo( const CFoo<T> & );
        void setBasePtr( const CBase<T> & );
        void swap( CFoo<T> & );
        CFoo<T> & operator = ( CFoo<T> );
        ...
        ~CFoo();

    private:

        typename type<CBase<T> >::shared_ptr m_ptrBase;
        int m_nParam;

};

template < typename T >
CFoo<T>::CFoo()
    :m_nParam(0)
// How shall I handle here the "m_ptrBase" class member? e.g point it to NULL?
{

}

template < typename T >
CFoo<T>::CFoo(const CBase<T> & refBase, int nParam)
    :m_ptrBase(refBase.clone()), // Is this initialization exception-safe?
    m_nParam(nParam)
{

}

template < typename T >
CFoo<T>::CFoo(const CFoo<T> & refFoo)
    :m_ptrBase(refFoo.m_ptrBase),
    m_nParam(refFoo.m_nParam)
{

}

template < typename T >
void CFoo<T>::setBasePtr( const CBase<T> & refBase ){
    // ??? I would like to do sth. like: m_ptrBase(refBase.clone())
}

template < typename T >
CFoo<T>::~CFoo(){
    // The memory is going to be freed by the smart pointer itself and therefore
    // the destructor is empty, right?
}

template < typename T >
void CFoo<T>::swap( CFoo<T> & refFoo ){
//does this here makes sense?
    using std::swap;

    swap(m_ptrBase, refFoo.m_ptrBase);
    swap(m_nParam, refFoo.m_nParam);

}

template < typename T >
CFoo<T> & CFoo<T>::operator = ( CFoo<T> copyFoo ){
    copyFoo.swap(*this);
    return (*this);
}

Ниже приведен пример того, чего я хотел бы достичь интуитивно.Сначала я заполняю контейнер CFoo<float> объектами, которые содержат умные указатели на производные классы, кроме другого целочисленного члена класса (обратите внимание, что все это только иллюстративно).

std::vector<CFoo<float> > my_bank;
for (int b=0; b < 3; b++){
   float x = b*sqrt(2);
   my_bank.push_back( new CFoo<float>( CChild1<float>::initType2(x), b) );
}

for (double s= 1.0; s<= 8.0; s *= 2.0){
    my_bank.push_back( new CFoo<float>( CChild2<float>::initType2(x), 0) );
 }

После того, как контейнер заполнен,Я хотел бы сделать некоторые операции, вызывая virtual функции, например doSomething, которые специализируются на каждом производном классе.

for (int i=0; i < (int)my_bank.size(); i++){
    int b = my_bank[i].m_nParam;
    CBase<float>* myChild = my_bank[i].m_ptrBase;

    myChild->doSomething( param1, param2, param3, ..., b);
}

1 Ответ

3 голосов
/ 09 ноября 2011

Я действительно не знаю, как подойти к этому. Я не понимаю половину перечисленных вами требований к интерфейсу, поэтому рассмотрите этот экспериментальный ответ, который может вообще не относиться к вашей проблеме.

Я предлагаю вам сказать мне, чего именно не хватает в моем подходе, и я могу изменить это. Сейчас я опущу шаблоны, так как они не имеют отношения к проблеме.

Итак, без лишних слов, самый простой старт использует контейнер умных указателей:

#include <vector>
#include <memory>

struct Base
{
  virtual void f();
};

typedef std::shared_ptr<Base> BasePtr;
typedef std::vector<BasePtr> BaseContainer;

struct DerivedA : Base
{
  virtual void f();
  // ...
};

// further derived classes

Использование:

int main()
{
  BaseContainer v;
  v.push_back(BasePtr(new DerivedB));
  v.push_back(BasePtr(new DerivedC(true, 'a', Blue)));

  BasePtr x(new DerivedA);
  some_func(x);
  x->foo()
  v.push_back(x);

  v.front()->foo();
}

Если у вас где-то есть какой-то автоматический объект, вы можете вставить копию:

DerivedD d = get_some_d();
v.push_back(BasePtr(new DerivedD(d)));

Для повторения:

for (BaseContainer::const_iterator it = v.begin(), end = v.end(); it != end; ++it)
{
  (*it)->foo();
}

Обновление: Если вы хотите инициализировать объект после построения, вы можете сделать что-то вроде этого:

{
  DerivedE * p = new DerivedE(x, y, z); 
  p->init(a, b, c);
  v.push_back(BasePtr(p));
}

Или, если функция init является виртуальной, еще проще:

v.push_back(BasePtr(new DerivedE(x, y, z)));
v.back()->init(a, b, c);

2-е обновление: Вот как может выглядеть производный объект:

struct DerivedCar : Base
{
  enum EType { None = 0, Porsche, Dodge, Toyota };

  DerivedCar(EType t, bool a, unsigned int p)
  : Base(), type(t), automatic_transmission(a), price(p)
  {
    std::cout << "Congratulations, you know own a " << names[type] << "!\n"; }
  }

private:
  EType type;
  bool automatic_transmission;
  unsigned int price;

  static const std::unordered_map<EType, std::string> names; // fill it in elsewhere
};

Использование: Base * b = new DerivedCar(DerivedCar::Porsche, true, 2000);

3-е обновление: Это не связано, просто иллюстрация того, как использовать таблицы поиска в пользу операторов switch. Предположим, у нас есть много похожих функций (одна и та же сигнатура), которые мы хотим использовать на основе некоторого целого числа:

struct Foo
{
  void do_a();
  void do_b();
  // ...

  void do(int n)
  {
    switch (n) {
      case 2: do_a(); break;
      case 7: do_b(); break;
    }
  }
};

Вместо переключателя мы можем зарегистрировать все функции в таблице поиска. Здесь я предполагаю поддержку C ++ 11:

struct Foo
{
  // ...
  static const std::map<int, void(Foo::*)()> do_fns;

  void do(int n)
  {
    auto it = do_fns.find(n);
    if (it != do_fns.end()) { (this->**it)(); }
  }
};

const std::map<nt, void(Foo::*)()> Foo::do_fns {
  { 3, &Foo::do_a },
  { 7, &Foo::do_b },
// ...
};

По сути, вы превращаете статический код в контейнер data . Это всегда хорошо. Теперь это легко масштабируется; вы просто добавляете новые функции на карту поиска по мере их появления. Не нужно снова касаться действительного do() кода!

...