Grzenio
Grzenio

Reputation: 36689

Specific memory management issue in C++

Imagine the following classes in java or C#:

class A
{
  B b;
  //some other stuff

  public A(B b) {this.b = b;}
}

class B
{
  A createA() {return new A(this); }
}

then we would use it e.g.

A complicatedCreateA()
{
  B = new B();
  return b.createA();
}

and the virtual machine / CLR would make sure we don't leak memory.

How can I implement a similar pattern in C++ so that I don't leak memory and reference cleaned resources?

EDIT: To make it more clear I am specifically worried about what happens if I call createA() more than once, and when the different objects A will have different lifetimes, e.g.:

A anotherMethod()
{
  B = new B();
  A a = b.createA();
  //use a locally, or use with yet another object C, etc.
  return b.createA();
}

I have the basic understanding how smart pointers work in C++. However, even if I do something like:

boost::shared_ptr<B> b(new B());

then I don't have access to this smart pointer from within B, so I can't pass it to A. And how otherwise A can make sure that the corresponding object B gets deleted not too late and not too early?

Upvotes: 1

Views: 158

Answers (5)

CashCow
CashCow

Reputation: 31455

It is not a simple case of saying "use smart pointers". B appears to be a factory of A. A has a pointer to the factory object that created it (and presumably can create more A-type objects from it).

If you want B to be reference-counted it has to be "intrusively" reference counted. This is usually done with some kind of addRef() / release() mechanism, and when release() is called the last time it usually performs a delete this

I might also assume that A is polymorphic in some way such that you can get an object to create more objects of its own type (not necessarily clones) through its abstract factory.

The question is, where did the first B come from that created the first A? Do you have a lot of these B objects and do you really want to get rid of it the moment your last A is deleted, or is its lifetime managed elsewhere.

With regards to the A, your B would create a new one with regular new, and then you would put it into a shared_ptr or whatever yourself.

Note that you could use a reference here, thus:

class A
{
 private:
    friend class B;

    explicit A( B const& b );

    B (const) & b_;

    A(const A&); // not implemented
 public:
    A* createNew() const;
    ~A();      
};

 A::A( B const& b )
     : b_( b )
 {
    b_.addRef(); // possibly, if reference counted
 }

 A::~A()
 {
    b_.release();
 }

 A* A::createNew() const
 {
    return b_.createA();
 }

for B

 A* B::createNew() const
 {
    return new A(*this); // addRef() will be invoked
 }

 void B::addRef() const
 {
    atomically_increment( &my_refCount );
 }

 void B::release() const
 {
    if( !atomically_decrement( &my_refCount ) )
        delete this;
 }

where B has a mutable my_refCount of the type that fits into the atomic increment/decrement call for your system.

Note that you can use boost shared_ptr for B too but will need to need to derive B from boost::enable_shared_from_this<B>. This is also "intrusive" but will do a lot of the work for you. Your A will now need to store a boost::shared_ptr<B> and B::createA() will call shared_from_this().

To do it this way, you will need to create your first B and put it into a shared_ptr to create the first A.

Upvotes: 1

Bo Persson
Bo Persson

Reputation: 92381

To make it more clear I am specifically worried about what happens if I call createA() more than once, and when the different objects A will have different lifetimes, e.g.:

A anotherMethod() {
    B b = new B();
    A a = b.createA();   //use a locally, or use with yet another object C, etc.           
    return b.createA();
} 

There is no need at all to create objects with new B();, you can just create them on the stack and their lifetimes will be handled automatically.

Just do it like this:

A anotherMethod() {
    B b;
    A a = b.createA();   //use a locally, or use with yet another object C, etc.           
    return b.createA();
} 

Unlike in some other languages, this is not references to heap objects but values that are copied. The copies will be destroyed when their respective lifetime is over. Automatically!

Upvotes: 0

Mike Seymour
Mike Seymour

Reputation: 254751

The exact equivalent gets a little bit complicated:

class A
{
    std::shared_ptr<B> b;
    //some other stuff

public:
    A(std::shared_ptr<B> const & b) : b(b) {}
};

class B : public std::enable_shared_from_this<B>
{
public:
    A createA() {return A(shared_from_this());}
};

A complicatedCreateA()
{
    std::shared_ptr<B> b = std::make_shared<B>();
    return b->createA();
}

Alternatively, you could avoid the shared_from_this malarkey by making createA a non-member (or static member, if it needs access to Bs privates), taking a shared pointer argument:

A createA(std::shared_ptr<B> const & b) {return A(b);}

A complicatedCreateA()
{
    std::shared_ptr<B> b = std::make_shared<B>();
    return createA(b);
}

Upvotes: 3

Seth Carnegie
Seth Carnegie

Reputation: 75150

Either allocate them only on the stack (preferable) or (for times when you can't) use smart pointers, which obviate the need for manual memory management and are exception-safe when used properly.

class B;

class A
{
public:
  std::unique_ptr<B> b;

  A(B b) : b(make_unique<B>(b)) { }
};

class B
{
public:
  std::unique_ptr<A> createA() { return make_unique<A>(*this); }
};

Then

std::unique_ptr<A> complicatedCreateA()
{
  B b; // or std::unique_ptr<B> bptr(make_unique<B>()); but here that's useless more work

  return b.createA();
}

And you don't need to call delete on the return value of createA or on the member A::b because whenever the unique_ptrs go out of scope, they will clean up the A* and B* they own.

Notice that you call make_unique<T> to make a unique_ptr. This has the advantage of being more exception-safe than plain new, though you can use that too, like : b(new B(b)) and return std::unique_ptr<A>(new A(*this));.

There is also the reference-counted shared_ptr for when you need to have multiple pointers to the same object.

Upvotes: 3

RoundPi
RoundPi

Reputation: 5947

If you have access to boost, you can use smart pointers. Depend what you want to achieve, you can either use boost::shared_ptr(reference counting one) or boost::scoped_ptr.

Have a look at: http://www.boost.org/doc/libs/1_48_0/libs/smart_ptr/smart_ptr.htm

Upvotes: 0

Related Questions