简体   繁体   中英

C++ idiom to avoid memory leaks?

In the following code, there is a memory leak if Info::addPart1() is called multiple times by accident:

typedef struct
{
}part1;

typedef struct
{
}part2;

class Info
{
    private:
    part1* _ptr1;
    part2* _ptr2;    

    public:
    Info()
    {
      _ptr1 = _ptr2 = NULL;
    }

    ~Info()
    {
      delete _ptr1; 
      delete _ptr2;
    }

    addPart1()
    {
       _ptr1 = new part1;         
    }

    addPart2()
    {
      _ptr2 = new part2;         
    }   
};


Info _wrapper;
_wrapper.addPart1();
_wrapper.addPart2();

Is there a C++ idiom to handle this problem ?

I could rewrite addPart1 and addPart2 like this to defend the MLK

addPart1()
{
  if(_ptr1 != NULL) delete _ptr1;
  _ptr1 = new part1;         
}

Is that a good solution?

Use a smart pointer such as boost:shared_ptr , boost:scoped_ptr is recommended to manage the raw pointer. auto_ptr is tricky to work with, you need pay attention to that.

You should read about the smart pointer idiom and about RAII . I suggest taking a look into the new technical report (TR1).
Take a good look here and here .
Also take a look at boost's smart pointers.
I recommend loki-lib 's SmartPtr or StrongPtr classes.

Bear with me here...

In the distant past, programmers used constructs like "jump" and "goto" for flow control. Eventually common patterns emerged and constructs like for, do/while, function call and try/catch emerged, and the spaghetti was tamed. Those named constructs give a lot more information about intent than a generic goto, where you have to examine the rest of the code for context to understand what it's doing. In the unlikely event you see a goto in modern code by a competent coder, you know something pretty unusual is going on.

In my opinion, "delete" is the "goto" of memory management. There are enough smart pointer and container classes available to the modern developer that there's very little reason for most code to contain a single explicit delete (other than in the smart pointer implementations of course). When you see a plain "delete" you get no information about intent; when you see a scoped_ptr/auto_ptr/shared_ptr/ptr_container you get a lot more.

ie the idiom should be to aspire to write delete-free code by using appropriate smart pointer types (as recommended by just about every other answer here).

Update 2013-01-27: I note Herb Sutter's excellent talk on C++11 includes some similar sentiments re delete free code.

Checking for nonzero pointer before delete is redundant. delete 0 is guaranteed to be a no-op.

A common way to handle this is

delete _ptr1;
_ptr1 = 0;
_ptr1 = new part1;

Zeroing the pointer ensures there won't be any dangling pointers for example in the case part1 construction throws an exception.

Your suggested fix will work (though of course you're still at risk for a memory leak if addPart2() is called twice). A much safer approach is to use scoped_ptr from the Boost library collection (www.boost.org), which is a container that acts like a pointer, but guarantees that its target is deleted when the container is destroyed. Your revised class would then look like

class Info
{
    private:
    boost::scoped_ptr<part1> _ptr1;
    boost::scoped_ptr<part2> _ptr2;    

    public:
    Info() {}  // scoped_ptrs default to null

    // You no longer need an explicit destructor- the implicit destructor
    // works because the scoped_ptr destructor handles deletion

    addPart1()
    {
      _ptr1.reset(new part1);
    }

    addPart2()
    {
      _ptr2.reset(new part2);         
    }   
};

As a general principle, it's a good idea to avoid writing code that requires you to explicitly delete pointers. Instead, try to use containers that do it automatically at the appropriate time. Boost is a good resource for this kind of thing.

All this assumes you have a reason ptr1_ and ptr2_ need to be pointers. If not, it's much better to make them ordinary objects; then you get memory management for free.

Use construction is initialization instead.

class Info
{
    private:
    part1* _ptr1;
    part2* _ptr2;    

    public:
    Info() : _ptr1(new part1), _ptr2(new part2)
    {
    }

    ~Info()
    {
      delete _ptr1; 
      delete _ptr2;
    }
};

But in this case you might as well create the parts on the stack, so no new and delete is required.

class Info
{
    private:
    part1 _part1;
    part2 _part2;    

    public:
    Info()
    {
    }

    ~Info()
    {
    }
};

But I guess you want the pointers to be lazy created, then I wouldn't suggest to create public class methods that takes care of the initializations. This should be handled internally in the class, when the class need to allocate them.

If you want it to have a lazy behavior you might consider this:

addPart1()
{
    if(_ptr1 == NULL) {
        _ptr1 = new part1;
    }
}

The way you suggested is also an alternative depending how you want it to behave. But other people have suggested better ways to do it, but as we really don't know why you made it this way and how the surrounding code works ...

I agree with the group that you should use some kind of smart pointer.

If you do decide to continue with bare pointers, be aware that your class above does not have a copy constructor defined by you. Therefore, the C++ compiler has defined one for you that will just do a simple copy of all the pointers; which will lead to a double delete. You'll need to define your own copy constructor (or at least create a stub private copy constructor if you don't think you need a copy constructor).

Info(const Info &rhs)
{
  _ptr1 = new part1[rhs._ptr1];
  _ptr2 = new part2[rhs._ptr2];
}

You will have a similar problem with the default assignment operator.

If you choose the correct smart pointer, these problems will go away. :)

Option 1: Use Java :)

Option 2: Use auto_ptr

std::auto_ptr<part1> _ptr1;
std::auto_ptr<part2> _ptr2;

public:
addPart1()
{
   _ptr1 = auto_ptr<part1>(new part1);
}

...

// no destructor is needed

你应该看看RAII

On the far extreme of possible ways to deal with memory leaks is the boehm garbage collector, a conservative mark & sweep collector. Interestingly, this can be used in addition to all the good advice offered in other answers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM