Smart pointers as class members for polymorphism

I am new to smart pointers, and I would be very grateful if someone could give me a hint if I correctly manage smart pointers as members of a class. More precisely, the solution that I would like to achieve belongs to the class of polymorphism and should be an ideal exception.

Given a container of dissimilar 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 later, you can call a member function of a certain derived class by executing: my_vector[0]->doSomething() .

  • What I would like to achieve is to add stack objects to the vector and still be able to do polymorphism. Intuitively sth. for example: CChild<float> obj1(1); my_vector.push_back(obj1) CChild<float> obj1(1); my_vector.push_back(obj1) . To solve this problem, I now use the Virtual Designer Idiom : CChild obj1(1); my_vector.push_back(obj1.clone()); CChild obj1(1); my_vector.push_back(obj1.clone()); .
    Note that in some my derived classes I have static member functions that create objects, for example: CChild<float> obj1 = CChild<float>::initType2(1);

  • Due to requirements problems, as well as for a clean interface, I have a new CFoo<T> class, which as a data member is a smart pointer to the CBase<T> class. The idea is that, in addition to other new private members, this class encapsulates / processes smart pointers to derived objects, so I am allowed to do sth. eg:
    CFoo<float> myfoo(CChild<float>::initType2(1)); my_vector.push_back(myfoo); . This means that the container now has type vector<CFoo<T> > instead of type vector<shared_ptr<CBase> >

In this context, I would like to know how to implement constructors for a class with smart pointers as members of the class? How about implementing operator = after copy-swap idiom? Below I will talk about some illustrations of my class:

 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? eg 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 is an example of what I would like to intuitively achieve. First, I fill the container with CFoo<float> objects that contain smart pointers to derived classes, in addition to another integer member of the class (note that this is all for illustrative purposes only).

 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 full, I would like to perform some operations by calling virtual functions, for example. doSomething that are specialized in every 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); } 
+4
source share
1 answer

I really don't know how to approach this. I don’t understand half of the interface requirements you specified, so consider this experimental answer, which may not concern your problem at all.

I suggest you tell me what is missing in my approach, and I can fix it. I am omitting the templates now, as they do not seem to be relevant to the problem.

Thus, without further use, the simplest beginning 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 

Using:

 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 have some kind of automatic object somewhere, you can paste a copy:

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

Iteration:

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

Update: If you want to initialize an object after building, 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 what the derived object looks 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 is unrelated, just an illustration of how to use lookup tables in favor of switch statements. Suppose we have many similar functions (the 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 a switch, we can register all the functions in the lookup table. Here I am 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 put static code in container data. This is always a good thing. Now it scales easily; you simply add new features to the search map as they become available. No more touching the actual do() code!

+2
source

Source: https://habr.com/ru/post/1380396/


All Articles