CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 10 of 10
  1. #1
    Join Date
    Apr 2010
    Location
    Dayton, OH
    Posts
    16

    Lightbulb Object storage & lifetime C++ Design

    Hi there! I chose to post here as I'm coding in pure C++ and that has a direct impact on my implementation. I apologize if I chose the incorrect location.

    I've been writing C++ professionally for about 6 years now. I've been reading books, taken classes, looked at and used other libraries in projects. In my spare time, I try out ideas and architectural concepts. This is for the purpose of learning, and this is one of those projects.

    At present, I have only myself to discuss these things with as I am the only coder at work, and none of my friends are programmers. When it comes to discussing C++, coding in general, architecture, design, etc, it's left me kind of lonely! I hope that's where you can help me out as I haven't had much luck elsewhere. I don't know if it's because I'm not asking the right questions, in the wrong place, or I smell funny.

    So, without further ado, here is my current adventure. After using the STL allocator, specializing it a few times, and studying other allocators (i.e in Boost), I decided I didn't like how object construction/destruction and storage were tied together. I believe they can be separated. A short while later, I came up with the following design:

    Code:
    // Responsible for creating/destroying an object at a certain memory location.
    // Creating an object consists of calling the object's Constructor
    // Destroying an object consists of calling the object's Destructor
    template<typename T>
    class ConstructionPolicy
    {
    public:
        ConstructionPolicy();
        ConstructionPolicy(const ConstructionPolicy<T>&);
        ConstructionPolicy<T>& operator=(const ConstructionPolicy<T>&);
    public:
        void Construct(T* pLocation);  // Construct an object at pLocation
        void Construct(T* pLocation, const T& ref);
        void Destruct(T* pLocation);    // Destruct an object at pLocation
    };
    
    // Two construction policies are considered equal if an object can be constructed 
    // with one policy and destructed with the other.  Comparing/assigning construction 
    // policies handling different types aren't supported (and I think shouldn't be?)
    template<typename T>
    bool operator==(const ConstructionPolicy<T>& l, const ConstructionPolicy<T>& r);
    template<typename T>
    bool operator!=(const ConstructionPolicy<T>& l, const ConstructionPolicy<T>& r);
    
    // Responsible for determining which ConstructionPolicy is appropriate for destroying
    // the specified object, and using it.  This is useful so we don't have to pass the 
    // object's Construction policy all over the place.
    template<typename T>
    void Destruct(T* pObject);
    Code:
    // Responsible for allocating storage for an object and releasing that storage
    template<typename T>
    class AllocationPolicy
    {
    public:
        AllocationPolicy();
        AllocationPolicy(const AllocationPolicy<T>&);
        template<typename U>
        AllocationPolicy(const AllocationPolicy<U>&);
        AllocationPolicy<T>& operator=(const AllocationPolicy<T>&);
        template<typename U>
        AllocationPolicy<T>& operator=(const AllocationPolicy<U>&);
    public:
        T* AddressOf(const T& object);    // Returns the address, in memory, to the object
        T* Allocate(size_t nCount);          // Allocates enough storage to hold nCount objects
        void Release(T* pLocation, size_t nCount);  // Release allocated memory
    };
    
    // Two allocation policies are considered equal if an memory allocated with one can
    // be released with the other.
    template<typename T, typename U>
    bool operator==(const AllocationPolicy<T>& l, const AllocationPolicy<U>& r);
    template<typename T, typename U>
    bool operator!=(const AllocationPolicy<T>& l, const AllocationPolicy<U>& r);
    
    // Responsible for determining which AllocationPolicy is appropriate for destroying
    // the specified object and using it.  This is useful so we don't have to pass the 
    // object's Allocation policy all over the place.
    template<typename T>
    void Release(T* pObject);
    Code:
    // Combines a AllocationPolicy and ConstructionPolicy for a type.  This basically
    // provides a lot of the same functionality a STL Allocator does, or at least I 
    // believe it does.
    template<typename T,
           typename A=AllocationPolicy<T>,
           typename C=ConstructionPolicy<T> >
    class Factory
    {
    public:
        typedef A alloc_t;
        typedef C construct_t;
    public:
        Factory();
        Factory(const Factory<T,A,C>&);
        Factory(const alloc_t&, const construct_t&);
        // I think some conversion constructors could be useful here, to handle
        // constructing with compatable allocators, similar to the template constructor
        // in the STL Allocator.  However, things get fuzzy here and I'm not sure what
        // what other constructors may be appropriate.
    public:
        T* Create(size_t nCount);   // Allocate & Construct nCount objects
        T* Create(size_t nCount, const T& ref);
        void Destroy(T* pLocation); // Destruct & Release objects at pLocation
    public:
        alloc_t& AllocationPolicy();
        construct_t& ConstructionPolicy();
    private:
        alloc_t m_ap;
        construct_t m_cp;
    };
    // I'm also stuck on the operators here, for the same reasons as I am on the
    // additional constructors above.  At present, all I can come up with is that two
    // factories are equal if the contained allocation and construction policies are
    // equal.  However, I feel this is too broad.
    // TODO: Appropriate operators.
    To keep things focused, I left out rebind, traits and value type typedefs that handle the pointers and references of T.

    The above is (mostly) simple enough to code, and pretty easy to extend to handle custom construction/allocation schemes for different types based on my experimentation so far. My question is: What flaws are in this design and can it be made better?

    I'm also struggling with the best way to handle the free floating Destroy and Release functions. A map of pointers to the best Destruct/Release member function seems overkill/slow, and allocating enough storage to hold the member function with the object results in hard to maintain/convoluted code (as such, this also applies to the storage of nCount for array destruction). Are there any better approaches?

    At my disposal is Visual C++ 2008, Boost 1.42, and std::tr1.

    Thanks and I highly appreciate any input you're willing to offer.

  2. #2
    Join Date
    Oct 2008
    Posts
    1,456

    Re: Object storage & lifetime C++ Design

    >> I decided I didn't like how object construction/destruction and storage were tied together. I believe they can be separated.

    the problem is that IMHO allocation and construction are not orthogonal concepts ( because you need to specify where an object is constructed before it's constructed ); therefore, the STL designers decided to give allocators both responsibilities of construction and allocation, although through different member functions.

  3. #3
    Join Date
    Apr 2010
    Location
    Dayton, OH
    Posts
    16

    Re: Object storage & lifetime C++ Design

    Thanks for the reply, superbonzo.

    I agree that you have to specify where an object is to be constructed before hand. You can't put the cart before the horse, but you can swap out the horses and carts independently.

    An object can be allocated without being constructed. For example, the std::vector can allocate enough space to hold N items, and not construct them until needed. Allocation can also occur in many different ways, independent of the object's construction. For example, allocation with new, malloc, HeapAlloc, Apache's memory pools, object pools, shared memory, memory mapped files, etc.

    The object being constructed doesn't care where, only that it has the room. In this way, I thought of separating the two policies. That allows me to mix and match different schemes without having to rewrite the entire Allocator interface for each combination.

  4. #4
    Join Date
    Jan 2006
    Location
    Singapore
    Posts
    6,765

    Re: Object storage & lifetime C++ Design

    I am curious though: what construction/destruction policies do you have in mind beyond the default allocator's use of placement new to invoke a constructor for the construct member function and the invocation of the destructor for the destroy member function?
    C + C++ Compiler: MinGW port of GCC
    Build + Version Control System: SCons + Bazaar

    Look up a C/C++ Reference and learn How To Ask Questions The Smart Way
    Kindly rate my posts if you found them useful

  5. #5
    Join Date
    Oct 2008
    Posts
    1,456

    Re: Object storage & lifetime C++ Design

    well, in all examples you mentioned the construction policy essentially consists simply in a call to placement new; my point is that if you want to add non trivial meaning to the construction process you'll need to know where the memory has been allocated ( I mean, to which resource it belongs ). So you'll probabily end up with pairs like HeapAllocationPolicy, HeapConstructionPolicy, and so on... defeating the purpose of policies of exploiting (nearly) all possible combinations of them.

    EDIT - sorry, I didn't read laserlight reply... and I'm curious too !

  6. #6
    Join Date
    Apr 2010
    Location
    Dayton, OH
    Posts
    16

    Re: Object storage & lifetime C++ Design

    Thanks guys, I appreciate the responses.

    Like I said before a lot of this is just experimentation as a learning experience on my part. So, this may very well end up with me saying "You're right, this is silly."

    One example would be some 3rd party object that requires some state or ID as part of construction. If for your application, you can auto-generate the ID or cache the state used, you can create a construction policy to handle that for you. Another example would be to some way wrap CoCreateInstance for COM objects. This would allow you a standard/unified interface for creating objects.

    So you'll probabily end up with pairs like HeapAllocationPolicy, HeapConstructionPolicy, and so on... defeating the purpose of policies of exploiting (nearly) all possible combinations of them.
    I'm not sure I follow why that has to be the case. The placement new only requires a pointer to the object to construct, unless I'm missing some requirement of placement new. It doesn't matter if it's a pointer from the heap, pool, or shared memory segment (or technically, even the stack, though this is dangerous). In those situations, you could custom allocation policies with the standard construction policy.

  7. #7
    Join Date
    Oct 2008
    Posts
    1,456

    Re: Object storage & lifetime C++ Design

    Quote Originally Posted by Night_Wulfe View Post
    I'm not sure I follow why that has to be the case. ...
    sorry, I've been a bit cryptic ...

    As you correctly said, placement new only requires a pointer to some properly sized memory, and you're right. I was thinking at possible alternative policies and I assumed that the only useful ones are those policies that know how the memory was allocated.

    For a trivial example, suppose, say, we want to attach to each (array of) object a timestamp (say, a std::clock_t) storing the construction time; in this case, the allocator would prepend sizeof(clock_t) bytes to each allocated block of memory, then the constructor policy would fill those bytes with the actual construction time (yes, there are some problems here, it's just an example)

    therefore, such a construction policy must exactly know how the object memory has been allocated. Now, I suppose this is a rather common scenario in which non trivial allocation is tied to non trivial construction, thus tying the two policies rendering them one single policy disguised as a pair of two policies.

    Quote Originally Posted by Night_Wulfe View Post
    One example would be some 3rd party object that requires some state or ID as part of construction ... This would allow you a standard/unified interface for creating objects.
    certainly that's possible in some specific cases, but how would that behave in those situations where the allocator gets rebinded ? for example, an std::list<T> will not invoke the allocator for T ( it will allocate and construct some "node" structure composing somehow with T, how would you cope with such a situation ? ); in any case, you have no control on when or what is constructed but only on where it's constructed...
    Last edited by superbonzo; April 13th, 2010 at 02:33 AM.

  8. #8
    Join Date
    Apr 2010
    Location
    Dayton, OH
    Posts
    16

    Re: Object storage & lifetime C++ Design

    certainly that's possible in some specific cases, but how would that behave in those situations where the allocator gets rebinded ? for example, an std::list<T> will not invoke the allocator for T ( it will allocate and construct some "node" structure composing somehow with T, how would you cope with such a situation ? ); in any case, you have no control on when or what is constructed but only on where it's constructed...
    Rebinding is something I thought about a little later. I realized that the only way this would work is if the ConstructionPolicy didn't support a rebind, and the Factory would always just use the default. As things came along, ConstructionPolicy started having all kinds of rules with it that would work, but there were so many it was hard to keep track of (and code for).

    After having played with actual code for this thing, running into some issues and the examples you've given, I give my design a thumbs down. Having little experience with designing libraries or complex applications, I'm still trying to balance complexity and extensibility with maintainability and practicality. Tough to do.

    This is exactly the discussion I was hoping to have. Your examples (such as the std::clock_t one) are dead-on in showing where the two policy approach isn't helpful, and in fact more of a pain. In fact, while experimenting with this design I kept running the very thing you're describing. I just didn't realize that was the actual issue at its core.

    Alternative issues I ran into were types that act like pointers (such as shared_ptr) and returning arrays of objects. However, I think this is a problem with std::allocators as well. For example, I can't think of how an allocator could be written such that it returned std::tr1::shared_ptr<T> simply because it's possible for allocators to return arrays of objects. Is this possible, or is it best that STL allocators work with standard pointer types only?

  9. #9
    Join Date
    Oct 2008
    Posts
    1,456

    Re: Object storage & lifetime C++ Design

    This is exactly the discussion I was hoping to have...
    I like these discussions too ! a think that (non trivially) flawed designs are as instructive as good ones; I'd really like a C++ book collecting all real world disasters in C++ programming ... it would be very insightful ( oh, I'm not saying your allocator would go there ... ).

    BTW, take a look here. In that article the author uses a similar approach but chooses a trait to represent per-type construction semantics ( with very limited possibilities of customization ) and, as you can see, the only real policy is the allocation policy ( = where and how something is allocated ).

  10. #10
    Join Date
    Apr 2010
    Location
    Dayton, OH
    Posts
    16

    Thumbs up Re: Object storage & lifetime C++ Design

    Quote Originally Posted by superbonzo View Post
    I like these discussions too ! a think that (non trivially) flawed designs are as instructive as good ones; I'd really like a C++ book collecting all real world disasters in C++ programming ... it would be very insightful ( oh, I'm not saying your allocator would go there ... ).
    I'd love such a book, myself!

    I wouldn't mind the allocator there, nor would I mind if you had been saying that Considering how low level memory allocation and object management is in a project, a poorly designed library around it that was used through out the project could be down right cataclysmic. Thankfully, this one hasn't seen the light of day in a real project.

    Quote Originally Posted by superbonzo View Post
    BTW, take a look here. In that article the author uses a similar approach but chooses a trait to represent per-type construction semantics ( with very limited possibilities of customization ) and, as you can see, the only real policy is the allocation policy ( = where and how something is allocated ).
    That very article is what inspired me to take it one step further. I feel I missed something important in it, though, and want to read through it again. My actual goal here is to use this experience to come up with something I can use that would be easily extended, even after being integrated into a project, and to help with debugging in the future.

    What I had hoped to implement was a set of objects that would allows me to easily customize allocation policies for a given type, even after that type had already been used all over the place. Ideally, it would allow me to do this in some places, and not others. Currently, my list of goals are as follows:

    1) A nice to have, but not necessary feature, would be the ability to associate other information with the allocated memory. Something similar to boost exception's ability to store any type of information as the exception is being caught and re-thrown. This would be used by the calling application. This actually is probably not very useful, and overly complex.

    2) A must is the ability to track memory statistics, such as bytes allocated, bytes released, total allocations. I'd like to implement this as an extension to the base set of classes so that I could test its extensibility and, if desired, use the core classes without the tracking capabilities.

    3) After some serious thinking, I decided that the interface should not use new/delete directly, but some wrappers. The reason for this is that if we overload new/delete, it would affect 3rd party libraries that use new/delete in their header files (e.g. Boost, Crypto++). One thought of syntax was something like:

    Code:
    T* pObject = Create<ObjectType>();
    T* pObject = Create<ObjectType>(ObjectType(Constructor, Parameters));
    Release(pObject);
    or, if I can use boost in_place_factory the way I hope I can:

    Code:
    T* pObject = Create<ObjectType>(Constructor, parameters);
    Release(pObject);
    The idea was that Create and Release would be a simple interface around my Factory, AllocationPolicy, and ConstructionPolicy classes. They would serve for "most of the time" use cases, but allow the classes for more fine grained control.

    4) A nice to have would be the ability to easily configure types that should return smart pointers instead of standard pointers. Personally, I love this as it would no longer let me get away with not using them. For example:

    Code:
    std::tr1::shared_ptr<ObjectType> pObject = Create<ObjectType>();
    T* pObject = Create<ObjectType>(); // Compiler error
    Release(pObject); // Same as pObject.release();
    5) A requirement, if 5 is doable, is the ability to treat special types the same way, for instance COM objects. I think with item 5 above, using type traits like is_base_of, and VC++'s __uuidof, this should be easy:

    Code:
    // Wraps CoReateInstance
    MSXML2::IXMLDOMDocument2Ptr pDoc = Create<MSXML2::IXMLDomDocument2>();
    6) Another requirement is that I can change the allocation policy for a type without a lot of work. Specializations of the policy classes were the idea i had for handling this one.

    7) It must track debug information, such as what file/line allocation occurred on. This would allow me to analyze what's "left" at program exit to debug memory leaks. I'd like to disable this in Release mode, but have the ability to turn it on in Release mode with an application configuration option for debugging purposes. It would be awesome if this occurred transparently with the help of some macros and temporary objects, but I have no problem typing __FILE__, __LINE__ everywhere if it can't be done.

    But alas, I know what I'd like to have and what I'll end up with will never be the same Back to the drawing board!

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured