How to avoid successive deallocations/allocations

2020-06-02 02:06发布

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

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

        b = new B(param1, param2, param3, param4);
    }
};

My goal: I need to maximize performance, which, in this case, means minimizing the amount of memory allocations.

The obvious thing to do here is to change B* b; to B b;. I see two problems with this approach:

  • I need to initialize b in the constructor. Since I don't know what b will be, this means I need to pass dummy values to B's constructor. Which, IMO, is ugly.
  • In calledVeryOften(), I'll have to do something like this: b = B(…), which is wrong for two reasons:
    • The destructor of b won't be called.
    • A temporary instance of B will be constructed, then copied into b, then the destructor of the temporary instance will be called. The copy and the destructor call could be avoided. Worse, calling the destructor could very well result in undesired behavior.

So what solutions do I have to avoid using new? Please keep in mind that:

  • I only have control over A. I don't have control over B, and I don't have control over the users of A.
  • I want to keep the code as clean and readable as possible.

10条回答
太酷不给撩
2楼-- · 2020-06-02 02:53

Just have a pile of previously used Bs, and re-use them.

查看更多
一夜七次
3楼-- · 2020-06-02 02:57

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;
}
查看更多
Animai°情兽
4楼-- · 2020-06-02 02:59

I'd go with boost::scoped_ptr here:

class A: boost::noncopyable
{
    typedef boost::scoped_ptr<B> b_ptr;
    b_ptr pb_;

public:

    A() : pb_() {}

    void calledVeryOften( /*…*/ )
    {
        pb_.reset( new B( params )); // old instance deallocated
        // safely use *pb_ as reference to instance of B
    }
};

No need for hand-crafted destructor, A is non-copyable, as it should be in your original code, not to leak memory on copy/assignment.

I'd suggest to re-think the design though if you need to re-allocate some inner state object very often. Look into Flyweight and State patterns.

查看更多
仙女界的扛把子
5楼-- · 2020-06-02 03:01

How about allocating the memory for B once (or for it's biggest possible variant) and using placement new?

A would store char memB[sizeof(BiggestB)]; and a B*. Sure, you'd need to manually call the destructors, but no memory would be allocated/deallocated.

   void* p = memB;
   B* b = new(p) SomeB();
   ...
   b->~B();   // explicit destructor call when needed.
查看更多
登录 后发表回答