vendredi 29 juillet 2016

Abstract types Meet std Containers

I've been trying to solve a design problem involving both an abstract type and its derived classes, as well as std containers. I have a working solution, but I would like to see if it can be improved. I apologize for the "..."s. But it is to make the design issue more clear.

// the base class
class B { 
private:
    ...
public:
    // the virtual function which makes B an abstract type
    virtual void func(...) = 0; 
    ...
};

// the 1st derived class
class D1 : public B { 
    ...
    void func(...) {...}
};

// the 2nd derived class
class D2 : public B {
    ...
    void func(...) {...}
};

Based on the problem, both D1 and D2 have to point to or contain some instances of B w/o knowing their concrete types. Currently, I'm using the point to approach, which means D1 and D2 contain some pointers to B.

In addition, those pointed-to instances of B are in fact shared among higher-level instances of B in recursive compositions. To make it clear, I'll expand the definition of D1.

class D1 : public B { 
private:
    B *d1_b1;
    B *d1_b2;
    ...
public:
    D1 (B *b1, B *b2) {...}
    B* func(...) {...}
    ...
};

Now, although d1_b1 and d1_b2 are shared by some higher-level instances of either D1 or D2, I think none of higher-level objects should maintain the ownership or lifetime of lower-level objects. Instead, I use an std container with unique_ptrs, for example:

std::vector<std::unique_ptr<B>> unique_b_objs;

The unique_b_objs is declared at the scope where the recursive construction is first called, and is passed in by reference and used recursively.

Now, every time an new object of either D1 or D2 is created, a corresponding unique_ptr is pushed back into unique_b_objs. Essentially, the raw pointers to objects of B are used in recursive composition, but the lifetime and ownership of the objects of B are managed by the std container.

I hope I've made it clear so far. It's working as expected. But I'm trying to get rid of the raw pointers. I can think of two approaches, but none of them work right now.

  • Use the "contain" approach. Essentially, all the B * becomes B in D1 and D2. But B is an abstract type, which means compilers won't even compile something like D1 (B b1, B b2) {...}
  • Use reference in the container. It can be done via the reference_wrapper. However, since the objects are created locally in recursions, the references become invalid out of the scope. This means we cannot transport references between recursive calls.

One working approach is to use shared_ptr. But it is unnecessary to share the ownership at every recursion step. And it also leads to performance overhead.

At this point, thank you for reading through the question. I hope I've made it clear. If you can think of some better approach than a container of unique_ptrs + raw pointers, please let me know.

Aucun commentaire:

Enregistrer un commentaire