ReferenceCountedObject vs std::shared_ptr


#1

As far as I can tell, the main difference between these two classes is that your type INHERITS FROM ReferenceCountedObject whereas shared_ptr OWNS your type. What this actually means is that with ReferenceCountedObject, your type gains a member variable that tracks the use count and a few member functions for modifying that use count.

Are there any plans to deprecate ReferenceCountedObject usage from the framework and switch to std::shared_ptr, the same way that std::unique_ptr has replaced ScopedPointer.

I’ve always found the whole ReferenceCountedObject<> and
using Ptr = ReferenceCountedObjectPtr<Foo> semantics really confusing, compared to just having a std::shared_ptr member variable in your class that’s using Foo.

here’s an example where a std::shared_ptr would be a much nicer return type:

template <typename NumericType>
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeLowPass (double sampleRate,
                                                                                          NumericType frequency,
                                                                                          NumericType Q)
{
    jassert (sampleRate > 0.0);
    jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
    jassert (Q > 0.0);

    auto n = 1 / std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
    auto nSquared = n * n;
    auto invQ = 1 / Q;
    auto c1 = 1 / (1 + invQ * n + nSquared);

    return *new Coefficients (c1, c1 * 2, c1,
                              1, c1 * 2 * (1 - nSquared),
                              c1 * (1 - invQ * n + nSquared));
}

that return *new Coefficients(...); just screams I'm a leak to the untrained eye, even though that return type of ::Ptr is actually ReferenceCountedObjectPtr<Coefficients>;

Perhaps ReferenceCountedObject and ReferenceCountedObjectPtr are relics from the pre-C++11 days of JUCE 3…

A lot of the new additions and updates to the library are very easy to read, use, and understand because they make use of modern C++. IMHO, ReferenceCountedObject and ReferenceCountedObjectPtr are 2 of the harder-to-use, read, and understand classes of the framework that could use some updating or replacing


#2

As far as I know, ScopedPointer has the same problem as auto_ptr, so there’s a stronger reason to replace it than “just” uniformity.

Generally, I agree and I prefer shared_ptr most of the time when I need reference counting.

For me, ReferenceCountedObjectPtr<Foo> has one advantage over shared_ptr<Foo>: With the former, you can create a new ReferenceCountedObjectPtr<Foo> inside a Foo member function, that points to this. With shared_ptr, that’s not possible, unless you use enable_shared_from_this, which (in my personal experience) is very error-prone.

Also there are performance considerations when you can’t use make_shared, because then the control block and your object may not be stored adjecent on the heap. Of course, this is only a problem when you can measure it.

I agree about your code example…in C++14 I would use an auto return type in this case, and then write return IIR::Coefficients<NumericType>::Ptr(new Coefficients(…)). Makes it clear at the point of calling new that the memory is being owned. Or create an equivalent to make_shared that wraps the call to new.


#3

Can you show an example of this? I don’t know enough about ReferenceCountedObjectPtr to know quite what you mean.


#4

I think (please correct me if I’m wrong), a trivially copyable object would survive a move using memcpy, as it is done in juce::Array for performance reasons. But I think with Array<std::shared_ptr> this would be a problem, since the non-intrusive reference count is not part of the object.
Especially for performance critical objects it might be beneficial to stick to ReferenceCountedObject over std::shared_ptr.


#5

I think since Tom’s ArrayBase changes it should be ok to store a juce::Array<std::shared_ptr<Foo>>.

Ownership is a bit easier to reason about with std::shared_ptr but you have to fully commit to it. The other huge advantage is is the thread-safe std::weak_ptr member.
The drawback of std::shared_ptr is that assignment of them isn’t implemented in a lock-free way on any platform that I know of (this might change in future though).

ReferenceCountedObjectPtr is lock-free but can be more difficult to reason about lifetime and has some other problems such as a race on assignment which can lead to a double deletion.


#6

std::shared_ptr does appear to be trivially copyable (at least at its current implementation) except for the reference counter, which will obviously not be updated or atomically preserved during the copy. Moving array of shared pointers exploiting memmove is fine in single threaded context but will cause troubles if same shared pointers passed around in parallel threads.

Also, it is recommended for the factories to return std::unique_ptr. A caller will then decide to either construct a std::shared_ptr if required, or keep it unique. Returning shared pointer from a factory will force a called to keep it shared.


#7

Array won’t internally use memmove for std::shared_ptr because std::is_trivially_copyable returns false. E.g. std::is_trivially_copyable<std::shared_ptr<Foo>> (see juce_ArrayBase.h).

The means the ref count will be correct if the array is resized.


#8

I am not really understanding the pattern that is supposed to be used with ReferenceCountedObject and ReferenceCountedObjectPtr.
is it something like this:

declare a Type that inherits from ReferenceCountedObject
instantiate that Type only on the Heap via ReferenceCountedObjectPtr<Type>

Maybe I’m missing something extremely simple or obvious, but it’s 100x easier to just write

struct Type { };

std::shared_ptr<Type> type = std::make_shared<Type>();

compared with:

struct Type : public ReferenceCountedObject
{
    using Ptr = ReferenceCountedObjectPtr<Type>;
};

Type::Ptr type = new Type();

and even then, I’m not sure if I got the pattern for ReferenceCountedObject right.

Is the whole reason for ReferenceCountedObject’s existence that whatever gets allocated on the heap needs to self-delete when no one is using it, and it needs to atomically increment/decrement that use count?


#9

Apart from the differences that have already been discussed above, it’s just a different design, intrusive vs shared. juce::ReferenceCountedObject also predates std::shared_ptr.

IMO if you don’t need lock-free assignments of shared pointers, use std::shared_ptr.
But in general, think carefully about if you actually need a shared pointer at all, perhaps the design would be better just transferring ownership? Shared pointers are mostly useful when you have multiple threads using them.


#10

This behaviour has been fixed in later JUCE (from July 2018), older implementation of Array container (before juce_ArrayBase.h introduction) moves and copies raw memory - not OK to be used to store stared_ptr.


#11

Yeah I know, which is why I referenced ArrayBase directly and the changed Tom made to make this work (previously it was called ArrayAllocationBase).

For people visiting the forum from now I think it’s a good idea to let them know they can use juce::Array with non-trivially-copyable types.


#12

One big reason to require ReferenceCountedObject: If you’re trying to pass a custom type around as a var, your class needs to inherit ReferenceCountedObject. There’s no way to accomplish that type of thing with a shared_ptr.