Scope pointer and ReferenceCountedObject tutorial


#1

Hi, it’s shameful that I come to ask some examples of the scoped pointer and reference counted object examples. Using JUCE only for the GUI and audio wrappers, I never really took the time to dig how to deal with counters and scope pointers. In short most of the time I build an object in the constructor and destroy it when the parent object is destructed. That is obviously not viable for larger projects with shared objects, references delayed callbacks etc… so:

  1. In the ReferenceCountedObject we have the following example:

[code] class MyClass : public ReferenceCountedObject
{
void foo();
// This is a neat way of declaring a typedef for a pointer class,
// rather than typing out the full templated name each time…
typedef ReferenceCountedObjectPtr Ptr;
};

MyClass::Ptr p = new MyClass();
MyClass::Ptr p2 = p;
p = nullptr;
p2->foo();[/code]

will p be destroyed if I do p2 = nullptr ? ([edit] Yes it does!) If so, I then guess I should put the pointer to nullptr in the destructor, right ? ([edit] If p is on the stack, no… if p is a class variable, destructing the object will release all the variables it does held including p. Writing explicitly “p = nullptr” in the destructor is therefore useless)

  1. I would like to create an AudioSampleBuffer, should I do the following:
typedef ReferenceCountedObjectPtr<AudioSampleBuffer> Ptr;

and then:

AudioSampleBuffer::Ptr ab = new AudioSampleBuffer(2, 1024);
  1. What about scope pointers ? How to code something like :

and are affectations also like the following ?

  1. When after a function call we do return a scope pointer, will it live until the scope of the function caller ? I guess yes…

  2. Is that correct: ScopedPointers should be used in order to return variables, affect and do some stuff which will end up with the stack at some time but countedObject are preferable for app life time variable such as sharing variables between threads, objects… variables owned by an object on the heap typically ?

  3. Is there some good examples somewhere related to the points above ? Did I missed something else important ?


#2

Probably the best way to understand both these classes would be to read something like Effective C++ by Scott Meyers, but here is my stab at a crash course primer.

Jules frequently refers to this as RAII (Resource Allocation is Initialization) style programming, but the underlying theory is simply ‘put resources, including memory allocations, in classes’.

To understand why, take a simple example:

SomeFunction()
{
    MyClass *var = new MyClass();

    <Do Some Stuff>
    
    delete var;
}

Generally speaking, this works fine, but there are some gotchas. For example, let’s say ‘Do Some Stuff’ get’s complicated and long, and has multiple exit paths. You can easily get in a case where you never reach “delete var”. Or, ‘Do Some Stuff’ could throw an exception and, again, delete var is never reached. This means memory is leaked. But in both cases, exiting the function and processing an exception, the destructors for any classes allocated on the stack will be called, so, enter ScopedPointer…

SomeFunction()
{
    ScopedPointer<MyClass> var = new MyClass();

    <Do Some Stuff>
 }

ScopedPointer is a class that wraps our simple pointer from the previous example. However we leave SomeFunction, the destructor for var will be called, and it will delete the instance of MyClass that we put into it. You can even do something like this:

SomeFunction()
{
    ScopedPointer<MyClass> var = new MyClass();

    <Do Some Stuff>

    var = new MyClass();

    <Do More Stuff>

    var = nullptr;

    <And still more stuff>

    var = new MyClass();

    <And some final stuff>
 }

Memory won’t leak, since ScopedPointer will delete previous contents (if any) when assigned. So ScopedPointer is just what it’s name implys, a class that can be used in place of a class pointer which will automatically clean things up for you whenever it goes out of scope. Since it is a template, you can use it with most types of classes.

ReferenceCountedObject and ReferenceCountedObjectPtr are a little different. ReferenceCountedObject, like ScopedPointer is a class intended to ‘self release’ objects, but instead of ‘release when you go out of scope’, the rule is ‘release when your reference count is decremented to zero’.

This is useful of you want to allocate an object, have that single instance shared in multiple places, and then automatically cleanup when the last ‘user’ signifies it is done with the object.

Objects you want to share inherit from ReferenceCountedObject and classes that use the shared instance store the reference in ReferenceCountedObjectPtr’s. ReferenceCountedObjectPtr takes care of incrementing and decrementing the reference count of the object for you, and when the count is decremented to 0, delete is called, freeing the memory.

I hope the above didn’t make it even more confusing. Good Luck.


#3

Great explanation, and should help a lot of people out!


#4

Thank you jfitzpat for this first answer concerning the scope pointers, some simple source code example is exactly what I’m looking for.
Ok and regarding my question #2 I guess there is no restriction to simply put a ScopedPointer in a class variable as well ? If only one object needs to deal with the AudioSampleBuffer, this is probably much simpler to use a ScopedPointer. Otherwise (and let me know if I’m wrong) I should declare my own class (f.e. CountedAudioSampleBuffer) inheriting from both AudioSampleBuffer and ReferenceCountedObject to then be able to declare a ReferenceCountedObjectPtr audioBuf; (remember AudioSampleBuffer does not inherit from ReferenceCountedObject per default)

Regarding question #4 : I just read we cannot return a scoped pointer, I guess we reached the limit of the scoped pointer and should consider using the ReferenceCountedObject in this case.


#5

[quote=“Xample”]
Regarding question #4 : I just read we cannot return a scoped pointer, I guess we reached the limit of the scoped pointer and should consider using the ReferenceCountedObject in this case.[/quote]

Yes. Scoped pointers are extremely handy when you have a need to new something inside a function. For keeping stuff alive over a long period, especially in cases where the resource may be shared between multiple consumers ReferenceCountedObject is probably a better fit.

As for making a ScopedPointer a class member, you can, but I’m not sure why you’d want to. If you want shared pointer behaviour but don;t want to modify the inheritance of a class, use the std/boost alternatives. Otherwise, as a simple rule of thumb[1] use ReferenceCountedObjects when the lifetime of an object exceeds a function.

[1] with all the usual disclaimers that thumbs shouldn’t really have an opinion.


#6

Bear in mind that std::unique_ptr can do some tricks that are impossible for ScopedPointer, so if you have C++11 support, you might want to prefer std::unique_ptr where possible. I’m looking forward to being able to use that in the library one day when there’s no need to support legacy compilers.


#7

Me too. If only Projucer had a strong debugger, full support for c++11, a text editor similar in functionality and performance to Sublime Text, and could build debug/release targets for all platforms, I could ditch this POS Visual Studio 2010 once and for all.


#8

Yes, you can use a scoped pointer as a member of a class, and it would serve similar purposes. Consider:

class MyClass
{
...
private:
    SomeClass *x;
}

MyClass::MyClass()
{
    x = new SomeClass();
}

MyClass::~MyClass()
{
    delete x;
}

This is actually very much like the first sample I gave below, allocate, use while we are alive, clean up. So, again, we can use RAII to manage our ‘resources’ (in this case a SomeClass):

class MyClass
{
...
private:
    ScopedPointer<SomeClass> x;
}

MyClass::MyClass()
{
    x = new SomeClass();
}

MyClass::~MyClass()
{
}

Less code for the same results. Which is typically a good thing.

You actually have more choices available to you, and it really depends on what you are trying to do. The comment you pointed to talked about Jule’s decisions regarding object ownership - which seem pretty reasonable to me. But ScopedPointer can still be useful. Let’s take the case where you don’t just don’t allocate something, but you test or add it to before returning, places where you get code like this:

SomeClass* GimmeOne (int widgets)
{
    SomeClass* s = new SomeClass();

    for (int n=0; n < widgets; ++n)
    {
        widget* w = GetWidget();
        if (w == nullptr)
        {
            delete s;
            return nullptr;
        }

        s->addWidget (w);
    }

    if (s->testWidgetCache() == false)
    {
        delete s;
        return nullptr;
    }

    return s;
}

Sorry, not very creative, but we’ve all wound up in cases where there are multiple failure points and we have to do cleanup there - the more the messier, and, of course, functions we call might throw exceptions. In a case like this could still use ScopedPointer, or something like it:

SomeClass* GimmeOne (int widgets)
{
    ScopedPointer<SomeClass> s = new SomeClass();

    for (int n=0; n < widgets; ++n)
    {
        widget* w = GetWidget();
        if (w == nullptr)
            return nullptr;

        s->addWidget (w);
    }

    if (s->testWidgetCache() == false)
        delete s;

    return s->release();
}

Again, not a very good example, we only dropped a few lines of code, but the underlying idea is that you can use the resource container object (in this case ScopedPointer) to take care of clean up through the body of your function, in the case of error returns or exceptions, then, at the end, when you have achieved success, return the contents bypassing the auto delete. In either case, at the receiving end you can do this:

ScopedPointer<SomeClass> s = GimmeOne (desiredWidgets);

Again, it is a big subject and I hope that short contrived snippets like this aren’t too confusing. As several people have noted, the standard template library has a set of RAII style containers too, and, as Jules notes, with C++ 2011, it is getting richer still.


#9

I’m glad it isn’t just me. I haven’t hated a dev tool as much as VC10 since MPW!


#10

I’m glad it isn’t just me. I haven’t hated a dev tool as much as VC10 since MPW![/quote]

It’s a love / hate relationship. The irony is that Visual Studio 2010 is currently the best / most productive IDE available if you can overlook the lack of c++11 support.