A shared, java-like, referenced Pointer


#1

I know there is ReferenceCountedObjectPtr, but it would be easier to have an template (like ScopedPointer) for an pointer which automatically deletes its object when there are 0 references to the same adress.
The advantage is to use existing classes with it, no need for subclass it with ReferenceCountedObject, and easily porting Java to C++ and backwards.
The address could be stored in a global singleton array which stores all addresses in a process.

ReferencedPointer a = new MyClass();
ReferencedPointer b = a;
a=0;
b=0; // delete


#2

It’s called boost::shared_ptr
http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/smart_ptr.htm


#3

It’s a common technique, though you have to be careful when you use shared_ptr.

With a ReferenceCountedObject, you can create multiple ReferenceCountedObjectPtrs from the same raw object pointer and it’ll all work fine, but with shared_ptr, if you accidentally dereference the object and then create a new shared_ptr from the raw object, you’ll end up with multiple ref counts and multiple deletions.


#4

Also, if you’re doing digital audio or anything which involves multiple threads, you need to be careful, because shared_ptr is not thread-safe, and this can cause hard-to-understand thread bugs in apparently thread-safe code if the destructor sometimes goes off in one thread and sometimes in another.

The last company I worked at, which had pretty serious C++ coding standards, prohibited all reference-counted C++ shared pointers unless you could prove there was no simpler, more obvious way to do it. I only remember seeing one case. Inevitably, removing this idea makes the code smaller and clearer.

People who grep up with Java use far too many pointers in their C++. Many times, passing small or medium things by value is just what you want, faster, smaller and with no memory management issues. If you start that way - making sure of course that your classes are copiable - you can then use pointers at the very end in the unlikely case that these tiny copies are a bottleneck.

This doesn’t work for non-copiable objects like e.g. database cursors or large items like samples.

If you really must use pointers, what are these objects? Why do they have such amorphous ownership? Isn’t there some single place that they can all live?

If these are static items that aren’t deleted and recreated, do you even need to delete them at all? Can they just be static and never deleted?


#5

[quote=“TomSwirly”]Also, if you’re doing digital audio or anything which involves multiple threads, you need to be careful, because shared_ptr is not thread-safe, and this can cause hard-to-understand thread bugs in apparently thread-safe code if the destructor sometimes goes off in one thread and sometimes in another.
[/quote]

Tom, good points. I’m putting out an opposing view here in hope that somebody will enlighten me if i’m wrong…

I’m under the impression that assignment and reset of a shared_ptr is atomic. This makes it very useful for sharing objects between threads and many uses of CriticalSections can be avoided with code like this:

shared_ptr<MyAudioProcessor> sharedProcessor;

MainThread:
   int x = 0;
   while (true)
   {
      if ((x++)%2 == 0)
        sharedProcessor = new MyAudioProcessor();
     else
        sharedProcessor.reset();
   }

AudioThread:
   shared_ptr<MyAudioProcessor> p = sharedProcessor; 
   if (p)
   {
     //  regardless of whether MainThread resets or assigns a new 'sharedProcessor' - its still available here as 'p'
       ... process some audio with 'p'
   }

#6

Tom, good points!

OBO, even when shared_ptr is atomic, you have tu use critical sections, to be sure that .reset and processing won’t be called at the same time


#7

I disagree. Please argue why you think so.

Anything that happens to the shared_ptr (reset and assignment) in the example is atomic. Either the p variable gets the value of a processor or it gets an empty pointer. In either case there are no race conditions.

Whatever the main thread does, will not affect the audio thread beyond the assignment of p. And p is always either a valid pointer or a null pointer.


#8

MainThread:

AudioThread:

this can happen paralell while p == sharedProcessor


#9

(Coming back to this a bit later…)

chkn appears to be correct. share_ptr has some thread-safety as detailed here - but that only handles either simultaneous reading, or simultaneous writing. If you’re trying to read and write at the same time from different threads, all bets are off.

Note also that this limited “thread safety” you get for free in shared_ptr does not work on absolutely every C++ platform, though it does work on the most common ones you’re likely to encounter - see the link for the details.

I have to say that I never trust the “automatic” thread safety I get this way - partly because I need to lock in my code anyway and it’s distracting to my mind to realize that some of the items I’m locking have their own limited thread safety. Better IMHO to have the locking be as explicit as possible, at the highest possible level, and for the shortest possible time, and simply ignore the thread_safety that my components might have.

Which is why I think Java’s locking for its Vector and Juce’s for its Array - which I know is mercifully optional :smiley: - is such a silly idea. I never want my nuts and bolts to be locking. I want to have as few locks (in Juce, CriticalSections) as I possibly can and know exactly what they’re doing. In particular, I really want to avoid thrashing my locks by dipping in and out of some locked component in, say, a loop, when I know that everything is locked already. (Locking the component before starting the loop does mostly fix that but it’s an extra piece that’s easy to forget…)


#10

Yes, i also have finallly realized that chkn is right.
Meanwhile i think that JUCE’s own intrusive ReferenceCountedObject has the thread safety properties i was looking for above:
http://rawmaterialsoftware.com/viewtopic.php?f=2&t=5831


#11

I know this discussion ended a while ago, but I just wanted to say that I’ve been wishing for a while that Juce supported the use of std::tr1::shared_ptrs. They come built into the g++ and VisualStudio compiler, and I use them frequently when it’s not obvious whether the caller or recipient of an object should be responsible for deleting it. For a long time I didn’t fully trust them, and I used a logging deleter to keep track if they were working correctly. But I’ve never had any problems with them, and it gives me a lot of piece of mind using them and knowing that my objects are going to be properly deallocated without me having to worry about it.

Maybe, I’m doing something wrong, but when I’m working with Juce, it bugs me a little that I can’t always define the way an object is going to be cleaned up when I’m allocating it. When I subclass a component and add more components to it, I have to remember to put deleteAllChildren() in my subclasses destructor or they’ll leak. When I swap out the look and feel, I have to remember to delete the look and feel ago at application shutdown. And so on… I’d much rather give a component over a shared_ptr and not have to go checking over my code again at the end of the day to make sure I didn’t forget to clean up my objects…


#12

There’s nothing wrong with shared_ptrs, and there’s nothing stopping you mixing them up with juce stuff… They’re not massively efficient though, and can be dangerous if not used carefully, so wherever I needed a ref-counted pointer, I’ve used ReferenceCountedObjectPointer class, which is a better option than shared_ptr in cases where you can make it inherit from the extra base class.

There’s certainly a good argument in favour of making look and feel objects ref-counted… but personally, I always tend to define them as part of my application object or a main window class, so that there’s no need to worry about allocation or cleanup, e.g.

[code]class MyApp
{
…etc

void initialise()
{
    LookAndFeel::setLookAndFeel (&myLook);
}

void shutdown()
{
    LookAndFeel::setLookAndFeel (0);
}

MyLookAndFeel myLook;

};[/code]

…that feels much neater to me.

(In fact, what I should do is to make sure that when the current default l+f is deleted, it calls setDefaultLookAndFeel (0), and then you wouldn’t even need to worry about doing anything in the shutdown code).