How to avoid successive deallocations/allocations in C++?

前端 未结 10 815
失恋的感觉
失恋的感觉 2021-02-07 18:26

Consider the following code:

class A
{
    B* b; // an A object owns a B object

    A() : b(NULL) { } // we don\'t know what b will be when constructing A

             


        
相关标签:
10条回答
  • 2021-02-07 19:13

    Are you sure that memory allocation is the bottleneck you think it is? Is B's constructor trivially fast?

    If memory allocation is the real problem, then placement new or some of the other solutions here might well help.

    If the types and ranges of the param[1..4] are reasonable, and the B constructor "heavy", you might also consider using a cached set of B. This presumes you are actually allowed to have more than one at a time, that it does not front a resource for example.

    0 讨论(0)
  • 2021-02-07 19:13

    Erm, is there some reason you can't do this?

    A() : b(new B()) { }
    
    void calledVeryOften(…) 
    {
        b->setValues(param1, param2, param3, param4); 
    }
    

    (or set them individually, since you don't have access to the B class - those values do have mutator-methods, right?)

    0 讨论(0)
  • 2021-02-07 19:14

    If B correctly implements its copy assignment operator then b = B(...) should not call any destructor on b. It is the most obvious solution to your problem.

    If, however, B cannot be appropriately 'default' initialized you could do something like this. I would only recommend this approach as a last resort as it is very hard to get safe. Untested, and very probably with corner case exception bugs:

    // Used to clean up raw memory of construction of B fails
    struct PlacementHelper
    {
        PlacementHelper() : placement(NULL)
        {
        }
    
        ~PlacementHelper()
        {
            operator delete(placement);
        }
    
        void* placement;
    };
    
    void calledVeryOften(....)
    {
        PlacementHelper hp;
    
        if (b == NULL)
        {
            hp.placement = operator new(sizeof(B));
        }
        else
        {
            hp.placement = b;
            b->~B();
            b = NULL;  // We can't let b be non-null but point at an invalid B
        }
    
        // If construction throws, hp will clean up the raw memory
        b = new (placement) B(param1, param2, param3, param4);
    
        // Stop hp from cleaning up; b points at a valid object
        hp.placement = NULL;
    }
    
    0 讨论(0)
  • 2021-02-07 19:18

    I liked Klaim's answer, so I wrote this up real fast. I don't claim perfect correctness but it looks pretty good to me. (i.e., the only testing it has is the sample main below)

    It's a generic lazy-initializer. The space for the object is allocated once, and the object starts at null. You can then create, over-writing previous objects, with no new memory allocations.

    It implements all the necessary constructors, destructor, copy/assignment, swap, yadda-yadda. Here you go:

    #include <cassert>
    #include <new>
    
    template <typename T>
    class lazy_object
    {
    public:
        // types
        typedef T value_type;
        typedef const T const_value_type;
        typedef value_type& reference;
        typedef const_value_type& const_reference;
        typedef value_type* pointer;
        typedef const_value_type* const_pointer;
    
        // creation
        lazy_object(void) :
        mObject(0),
        mBuffer(::operator new(sizeof(T)))
        {
        }
    
        lazy_object(const lazy_object& pRhs) :
        mObject(0),
        mBuffer(::operator new(sizeof(T)))
        {
            if (pRhs.exists())
            {
                mObject = new (buffer()) T(pRhs.get());
            }
        }
    
        lazy_object& operator=(lazy_object pRhs)
        {
            pRhs.swap(*this);
    
            return *this;
        }
    
        ~lazy_object(void)
        {
            destroy();
            ::operator delete(mBuffer);
        }
    
        // need to make multiple versions of this.
        // variadic templates/Boost.PreProccesor
        // would help immensely. For now, I give
        // two, but it's easy to make more.
        void create(void)
        {
            destroy();
            mObject = new (buffer()) T();
        }
    
        template <typename A1>
        void create(const A1 pA1)
        {
            destroy();
            mObject = new (buffer()) T(pA1);
        }
    
        void destroy(void)
        {
            if (exists())
            {
                mObject->~T();
                mObject = 0;
            }
        }
    
        void swap(lazy_object& pRhs)
        {
            std::swap(mObject, pRhs.mObject);
            std::swap(mBuffer, pRhs.mBuffer);
        }
    
        // access
        reference get(void)
        {
            return *get_ptr();
        }
    
        const_reference get(void) const
        {
            return *get_ptr();
        }
    
        pointer get_ptr(void)
        {
            assert(exists());
            return mObject;
        }
    
        const_pointer get_ptr(void) const
        {
            assert(exists());
            return mObject;
        }
    
        void* buffer(void)
        {
            return mBuffer;
        }
    
        // query
        const bool exists(void) const
        {
            return mObject != 0;
        }
    
    private:
        // members
        pointer mObject;
        void* mBuffer;
    };
    
    // explicit swaps for generality
    template <typename T>
    void swap(lazy_object<T>& pLhs, lazy_object<T>& pRhs)
    {
        pLhs.swap(pRhs);
    }
    
    // if the above code is in a namespace, don't put this in it!
    // specializations in global namespace std are allowed.
    namespace std
    {
        template <typename T>
        void swap(lazy_object<T>& pLhs, lazy_object<T>& pRhs)
        {
            pLhs.swap(pRhs);
        }
    }
    
    // test use
    #include <iostream>
    
    int main(void)
    {
        // basic usage
        lazy_object<int> i;
        i.create();
        i.get() = 5;
    
        std::cout << i.get() << std::endl;
    
        // asserts (not created yet)
        lazy_object<double> d;
        std::cout << d.get() << std::endl;
    }
    

    In your case, just create a member in your class: lazy_object<B> and you're done. No manual releases or making copy-constructors, destructors, etc. Everything is taken care of in your nice, small re-usable class. :)

    EDIT

    Removed the need for vector, should save a bit of space and what-not.

    EDIT2

    This uses aligned_storage and alignment_of to use the stack instead of heap. I used boost, but this functionality exists in both TR1 and C++0x. We lose the ability to copy, and therefore swap.

    #include <boost/type_traits/aligned_storage.hpp>
    #include <cassert>
    #include <new>
    
    template <typename T>
    class lazy_object_stack
    {
    public:
        // types
        typedef T value_type;
        typedef const T const_value_type;
        typedef value_type& reference;
        typedef const_value_type& const_reference;
        typedef value_type* pointer;
        typedef const_value_type* const_pointer;
    
        // creation
        lazy_object_stack(void) :
        mObject(0)
        {
        }
    
        ~lazy_object_stack(void)
        {
            destroy();
        }
    
        // need to make multiple versions of this.
        // variadic templates/Boost.PreProccesor
        // would help immensely. For now, I give
        // two, but it's easy to make more.
        void create(void)
        {
            destroy();
            mObject = new (buffer()) T();
        }
    
        template <typename A1>
        void create(const A1 pA1)
        {
            destroy();
            mObject = new (buffer()) T(pA1);
        }
    
        void destroy(void)
        {
            if (exists())
            {
                mObject->~T();
                mObject = 0;
            }
        }
    
        // access
        reference get(void)
        {
            return *get_ptr();
        }
    
        const_reference get(void) const
        {
            return *get_ptr();
        }
    
        pointer get_ptr(void)
        {
            assert(exists());
            return mObject;
        }
    
        const_pointer get_ptr(void) const
        {
            assert(exists());
            return mObject;
        }
    
        void* buffer(void)
        {
            return mBuffer.address();
        }
    
        // query
        const bool exists(void) const
        {
            return mObject != 0;
        }
    
    private:
        // types
        typedef boost::aligned_storage<sizeof(T),
                    boost::alignment_of<T>::value> storage_type;
    
        // members
        pointer mObject;
        storage_type mBuffer;
    
        // non-copyable
        lazy_object_stack(const lazy_object_stack& pRhs);
        lazy_object_stack& operator=(lazy_object_stack pRhs);
    };
    
    // test use
    #include <iostream>
    
    int main(void)
    {
        // basic usage
        lazy_object_stack<int> i;
        i.create();
        i.get() = 5;
    
        std::cout << i.get() << std::endl;
    
        // asserts (not created yet)
        lazy_object_stack<double> d;
        std::cout << d.get() << std::endl;
    }
    

    And there we go.

    0 讨论(0)
提交回复
热议问题