简体   繁体   中英

Smart pointers as class members for polymorphism

I'm new to smart pointers and I would be really grateful if somebody could give me a hint whether the way I'm handling smart pointers as class members is correct. More precisely, the solution that I would like to achieve is in the context of class polymorphism and should be ideally exception-safe.

Given a container of heterogeneuous objects ( std::vector<shared_ptr<CBase> > my_vector ), the usual way to add elements is: my_vector.push_back( shared_ptr<CBase>(new CChild(1))) , so that later on, one can call the member function of the specific derived class by doing: my_vector[0]->doSomething() .

  1. What I would like to achieve is to add stack objects to the vector and still being able to do polymorphism. Intuitively sth. like: CChild<float> obj1(1); my_vector.push_back(obj1) CChild<float> obj1(1); my_vector.push_back(obj1) . To solve that, I'm using now the Virtual Constructor Idiom : CChild obj1(1); my_vector.push_back(obj1.clone()); CChild obj1(1); my_vector.push_back(obj1.clone()); .
    Note that in some of my derived classes, I've static member functions that create objects, eg: CChild<float> obj1 = CChild<float>::initType2(1);

  2. Because of requirement issues and also to have a clean interface, I've now a new class CFoo<T> that has as data member a smart pointer to the CBase<T> class.
    The idea is that besides containing other new private members, this class encapsulates/handles the smart pointers to the derived objects, such that I'm allowed to do sth. like:
    CFoo<float> myfoo(CChild<float>::initType2(1)); my_vector.push_back(myfoo); . This means that the container is now of type vector<CFoo<T> > instead of type vector<shared_ptr<CBase> >

It's in this context, that I would like to know how to implement the constructors for a class with smart pointers as class members? What about the implementation of the operator = following the copy-swap idiom? Below, I give some ilustrations of my class design:

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

Below an example on what I would like to intuitively achieve. First, I fill the container with CFoo<float> objects that contain smart pointers to derived classes, besides another integer class member (Note that all this is only illustrative).

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

Once, the container is filled, I would like to do some operations, calling to virtual functions, eg doSomething that are specialized in each derived class.

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

I really don't know how to approach this. I don't understand half the interface requirements you've listed, so consider this an experimental answer that may not relate to your problem at all.

I suggest that you tell me what exactly is missing from my approach, and I can amend it. I'll omit templates for now, since they don't seem to be relevant to the problem.

So, without further ado, the simplest start uses a container of smart pointers:

#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

Usage:

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

If you happen to have some automatic object somewhere, you can insert a copy:

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

To iterate:

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

Update: If you want to initialize an object after construction, you can do something like this:

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

Or, if the init function is virtual, even simpler:

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

2nd Update: Here's how a derived object might look like:

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

Usage: Base * b = new DerivedCar(DerivedCar::Porsche, true, 2000);

3rd Update: This one is unconnected, just an illustration of how to use lookup tables in favour of switch statements. Suppose we have lots of similar functions (same signature) that we want to use based on some integer:

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

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

Instead of the switch, we can register all functions in a lookup table. Here I'm assuming C++11 support:

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 },
// ...
};

Basically, you turn static code into container data . That's always a Good Thing. This is now easily scalable; you just add new functions to the lookup map as they come along. No need to touch the actual do() code again!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM