如何避免C++中的连续解除分配/分配?

Eti*_*mps 13 c++ oop optimization memory-management

请考虑以下代码:

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

    void calledVeryOften(…)
    {
        if (b)
            delete b;

        b = new B(param1, param2, param3, param4);
    }
};
Run Code Online (Sandbox Code Playgroud)

我的目标:我需要最大限度地提高性能,在这种情况下,这意味着最大限度地减少内存分配量.

这里显而易见的事情是B* b;改为B b;.我看到这种方法存在两个问题:

  • 我需要b在构造函数中初始化.由于我不知道b将会是什么,这意味着我需要将虚拟值传递给B的构造函数.哪个,IMO,很难看.
  • calledVeryOften(),我将不得不做这样的事情:b = B(…),这有两个原因:
    • 析构函数b不会被调用.
    • 将构造B的临时实例,然后将其复制到b,然后将调用临时实例的析构函数.可以避免复制和析构函数调用.更糟糕的是,调用析构函数很可能导致不良行为.

那么我必须避免使用哪些解决方案new?请记住:

  • 我只能控制A.我无法控制B,而且我无法控制A的用户.
  • 我希望尽可能保持代码的清晰和可读性.

Kla*_*aim 8

只需保留b所需的内存(通过池或手动),并在每次删除/ new时重复使用,而不是每次重新分配.

示例:

class A
{
    B* b; // an A object owns a B object
    bool initialized;
public:
    A() : b( malloc( sizeof(B) ) ), initialized(false) { } // We reserve memory for b
    ~A() { if(initialized) destroy(); free(b); } // release memory only once we don't use it anymore

    void calledVeryOften(…)
    {
        if (initialized)
            destroy();

        create();
    }

 private:

    void destroy() { b->~B(); initialized = false; } // hand call to the destructor
    void create( param1, param2, param3, param4 )
    {
        b = new (b) B( param1, param2, param3, param4 ); // in place new : only construct, don't allocate but use the memory that the provided pointer point to
        initialized = true;
    }

};
Run Code Online (Sandbox Code Playgroud)

在某些情况下,Pool或ObjectPool可以更好地实现相同的想法.

然后构造/销毁成本将仅取决于B类的构造函数和析构函数.

  • 为什么要使用malloc&free?为什么不只是一个矢量<char>? (2认同)

GMa*_*ckG 8

我喜欢Klaim的答案,所以我写得很快.我没有声称完美的正确性,但它看起来对我很好.(即它唯一的测试是main下面的样本)

它是一个通用的惰性初始化器.对象的空间分配一次,对象从null开始.然后create,您可以覆盖以前的对象,而无需新的内存分配.

它实现了所有必要的构造函数,析构函数,复制/赋值,交换,yadda-yadda.干得好:

#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;
}
Run Code Online (Sandbox Code Playgroud)

在您的情况下,只需在您的班级中创建一个成员:lazy_object<B>您就完成了.没有手动发布或制作复制构造函数,析构函数等.一切都在你漂亮的小型可重用类中得到了解决.:)

编辑

删除了矢量的需要,应该节省一点空间和什么不.

编辑2

这使用aligned_storagealignment_of使用堆栈而不是堆.我使用了boost,但TR1和C++ 0x都存在此功能.我们失去了复制的能力,因此无法进行交换.

#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;
}
Run Code Online (Sandbox Code Playgroud)

我们走了.


Kor*_*icz 5

如何为B分配一次内存(或者为其最大可能的变体)并使用新的位置

A会存储char memB[sizeof(BiggestB)];B*.当然,您需要手动调用析构函数,但不会分配/取消分配内存.

   void* p = memB;
   B* b = new(p) SomeB();
   ...
   b->~B();   // explicit destructor call when needed.
Run Code Online (Sandbox Code Playgroud)