简体   繁体   中英

C++ Pointer data members: Who should delete them?

So say I have a class like this:

class A {
    public:
        A( SomeHugeClass* huge_object)
            : m_huge_object(huge_object) {}
    private:
        SomeHugeClass* m_huge_object;        
};

If someone uses the constructor like this:

A* foo = new A(new SomeHugeClass());

Who's responsibility is it to call delete on the object newed in the constructor? In this case, the scope in which the A constructor was called can only delete foo since the SomeHugeClass is anonymous.

However, what if someone uses the constructor like this?

SomeHugeClass* hugeObj = new SomeHugeClass();
A* foo = new A(hugeObj);

Then, the caller can call delete hugeObj at some point, right?

Does this implementation of A leak memory on destruction?

I'm working on a project with a lot of object composition done this way and as much as I would love to use smart pointers, I have to talk to the project leads about changing old code to take advantage of that before I can.

I try to follow this simple rule whenever it is possible: The one who calls new should call delete as well. Otherwise the code soon becomes too messy to keep track of what is deleted and what is not.

In your case, if A::A receives the pointer, it must not delete it. Think of this simple case:

SomeHugeClass* hugeObj = new SomeHugeClass();
A * a1 = new A(hugeObj);
A * a2 = new A(hugeObj);

Class A can not know who else is using that pointer!

If you want class A to take care of the pointer, it should create it itself.

Of course, you could handle both cases, but that might be an overkill, something like this:

A::A() : own_huge_object(true) {
    m_huge_object = new SomeHugeClass();
}
A::A(SomeHugeClass * huge_object ) : own_huge_object(false) {
    m_huge_object = huge_object;
}
A::~A() { if(own_huge_object) delete m_huge_object; }

In your example caller should be responsible for deleting huge_object because constructor could throw exception and destructor of A will not be called. And your implementation has a memory leak since nobody calls delete now.

You could use shared_ptr as follows:

class A {
    public:
        A( shared_ptr<SomeHugeClass> huge_object)
            : m_huge_object(huge_object) {}
    private:
        shared_ptr<SomeHugeClass> m_huge_object;        
};

In this case you shouldn't care about deleting SomeHugeClass .

In order for this line below

A* foo = new A(new SomeHugeClass());

not to result in memory leak, you can make sure to free memory pointed to by m_huge_object in the destructor of class A.

The class A definition can look something like below:

class A {
  public:
    A(SomeHugeClass* huge_object)
        : m_huge_object(huge_object) {}
    ~A() { delete m_huge_object; }
  private:
    SomeHugeClass* m_huge_object;        
};

What I don't like about the above is that I prefer whoever allocates memory should also be the one responsible to free that memory. Also, there's the problem that Kirill pointed out. So keep your definition of class A, and the code can be as simple as:

SomeHugeClass* hugeObj = new SomeHugeClass();
A* foo = new A(hugeObj); 
//some code
delete hugeObj;
delete foo;

This is more a design problem. Traditionnaly, when you pass non-const pointers to a constructor, the object should release the pointer. Pass a const reference if you intend to keep a copy.

An alternate design is something along these lines: SomeHugeClass typically is not that huge (eg. a pointer), but owns a huge amount of memory:

class A
{
    SomeHugeClass m_;

public:
    A(SomeHugeClass x) { m_.swap(x); }
};

This design is possible if SomeHugeClass implements an efficient swap (swapping the pointers). The constructor makes a copy of x before swapping it into m_ , and if you pass a temporary object to the constructor, the copy may be (and usually will be) elided by the compiler.

Note that SomeHugeClass can be replaced here by smart_pointer<SomeHugeClass> giving the semantics you want when you pass smart_pointer(new SomeHugeClass()) to the constructor, since it is temporary.

EDIT (for clarity...): Final code may look like

class A
{
    smart_ptr<SHC> m_;

public:
    A(smart_ptr<SHC> x) { m_.swap(x); }
};

which has the required behavior when you call A(new SHC(...)) (no copy, and deletion when A gets destructed), and when you call A(smart_ptr<SHC>(a)) (copy of a is performed, and released when A gets destructed).

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