Getting rid of ScopedPointer

You really should use make_unique - it’s safe, whilst wrapping like this isn’t (sure the chances of a failure in this logic is low but you know what I mean), and it uses the same memory block for the unique_ptr gubbins and the allocated object, which is more efficient in cache line terms than having a separate object and unique_ptr.

Do you really use *p rather than p-> in your code and hence that operator help clarity?

Can you give some examples of this? I’ve actually found the opposite that code with std::unique_ptr is actually more compact. And it certainly is safer.

I do agree it takes a long time though. I spent nearly a day updating our code base. The good news is that std::unique_ptr is unlikely to change for many years now it’s part of the standard.

1 Like

Yeah, +1 to Dave’s comment there - it’s really tedious to change them but I didn’t find the results to be any uglier.

The one place which I did find a bit annoying was where we had code like this

addAndMakeVisible (myMember = new SomeComponent());

…and rather than breaking that into two statements, ended up writing them as

addAndMakeVisible (*(myMember = std::make_unique<SomeComponent>()));

…which is a bit clunky.

I did consider adding a new addAndMakeVisible method which could take a std::unique_ptr, but that’d be quite confusing as it’d make it look as if the function takes ownership, when in fact it doesn’t.

1 Like

I am getting a bit confused by all this. I also updated my codebase to use unique_ptr everywhere. It was some work, but we’ve seen it coming for a long time, so no complaints here.

I do wonder about addAndMakeVisible as well as component callbacks regarding unique_ptr. Will these continue to use raw pointers? I remember reading somewhere in some c++ docs that the idea of unique_ptr is to have only one copy of each unique pointer “value” so ownership is absolutely clear and that’s why it was named unique_ptr. Maybe I’m totally misunderstanding this, but isn’t the the goal to get rid of raw pointers everywhere?. Would that mean in the future shared_pointers should be used for child components? I just see a lot of unique_ptr get() calls in my code now and that seems to be against the whole idea and I do wonder whether more smart pointer changes are coming to Juce in the future.

1 Like

That’s something I was wondering too, how referencing will work.

  • ptr.get() => back to raw pointers
  • std::unique_ptr<Foo>& getFooReference() => WTF?

The std::shared_ptr has the nice std::weak_ptr, and in JUCE there is Component::SafePointer and WeakReference
But what would be the consistent way to grant access to a std::unique_ptr?

Exactly. Using get() sometimes feels like using a raw pointer as a weak reference which seems dangerous.

How about using const reference of std::unique_ptr?

void test(const std::unique_ptr<Foo> &data)
{
  auto *ptr = data.get(); // returns non-const pointer to Foo
}

Here is a discussion about the topic on StackOverflow:

What I see in there (and the linked article inside): “It’s ok to use raw pointers if a method does not care about object lifetime.” However I’m still scared an owner might exit the unique_ptr scope and my raw pointer would become invalid. Obviously anytime a raw pointer is stored it is unclear when it’ll become invalid. I was hoping smart pointers could completely msolve this issue somehow.

In the addAndMakeVisible case a component stores raw pointers to its children in an Array (childComponentList). For the component there is no guarantee that those pointers will stay valid, but it is assumed child components are owned by the parent in the user code. I think there is an exception with some container components where the content is owned by the container. I wonder whether modern C++ could provide a better solution somehow.

1 Like

The confusion is because an important word is omitted from the common advice:

Don’t use raw pointers, use smart pointers instead. (Again, this is bad advice.)

Better advice is:

Don’t use owning raw pointers, use smart pointers instead.

Raw pointers are still valuable in at least one specific circumstance, namely, optionally pointing to (but not owning) an object. A good example of this is the UndoManager* parameter to the AudioProcessorValueTreeState constructor. If you want to use an undomanager, pass a non-null pointer to an UndoManager which is owned elsewhere. If you don’t need an undomanager, pass nullptr.

That being said, raw pointers are still a bit yucky from a type-checking perspective, as they might point to a single element or the beginning of an array of elements, and there’s no way to distinguish between the two cases from the type of the pointer. If you need to optionally point to an array, something like std::span is probably a better choice.

1 Like

Come to think of it, a raw pointer is probably a good option if you need a ‘reseatable reference’ too.

The only way to avoid an object from being deleted while referenced would be to stop the unique_ptr destruction, i.e. locking the thread that tries to destroy the unique_ptr until all other references are gone (not a nice option).

This can be done without locking using a shared_ptr. Here the ownership is not tied to the unique_ptr, but is floating between all shared_ptrs. And you can have a weak_ptr, that does not extend the lifetime. But while you are using it, you call lock(), so the weak_ptr is converted into a shared_ptr, so it will stay alive while it is being used.

It comes at the price, that the destruction can also happen on any thread, so in connection with Audio threads, you need extra measures to remedy that, i.e. an auto release pool.

unique_ptr has no additional storage besides the raw pointer inside. So, make_unique has no particular performance implications. (make_shared does potentially do the optimization where the shared_ptr’s internals are placed in memory close to the pointed to object. It’s not required by the standard, though.)

std::reference_wrapper? Doesn’t solve the null case though.

In general, if you see a raw pointer, you should assume it can be nullptr and should check for that. In most cases, you’re better off passing by reference or better still const reference.

I’m weary of the mention of std::shared_ptr. To me, share_ptr implies threading as that makes it possible to reason about lifetime across threads. For more general cases, especially in the case of Components that can have only one parent, std::unique_ptr is a much better fit and easier to reason about.

If your methods accept references to objects, the syntax is the same as a raw pointer:

auto myObj = std::make_unqiue<Object>();
functionThatTakesAnObject (*myObj);

I agree 100%! shared_ptr comes at a price, and it’s much easier without them it in the context of Components, since they are usually locked to the message thread anyway.

unique_ptr has no additional storage besides the raw pointer inside

This is incorrect. If you don’t use a custom destructor then most implementations will keep the size of the unique_ptr identical to the same of a raw pointer, but if you use a custom destructor then the unique_ptr has to store that somewhere (and that ‘somewhere’ is on the stack, as part of the unique_ptr object).

it uses the same memory block for the unique_ptr gubbins and the allocated object, which is more efficient in cache line terms than having a separate object and unique_ptr.

I believe @cesare is confusing unique_ptr with shared_ptr. unique_ptr doesn’t store any extra gubbins on the heap, but shared_ptr does store a control block on the heap, so using make_shared is sometimes an optimisation because it reduces the total number of allocations, and keeps refcounts close to the managed object in memory.

The main benefits of make_unique are:

  • not having to mention the typename twice
    unique_ptr<Foo> { new Foo };
    vs
    make_unique<Foo>();
    
  • Improved exception safety
    myFunction (std::unique_ptr<Foo> { new Foo },
                std::unique_ptr<Bar> { new Bar });
    
    If the compiler decided to order the function calls
    Foo constructor -> Bar constructor -> unique_ptr<Foo> constructor -> unique_ptr<Bar> constructor
    
    …and the Bar constructor were to throw, you’d leak a Foo instance. The make_unique version doesn’t have this problem.

Ah, muddling up shared and unique. Good spot.

A sane person wouldn’t use unique_ptr with a custom deleter, though… :wink:

L E G A C Y
A P I S

There are better options for making legacy APIs work in a modern C++ context than abusing unique_ptr.

I don’t think custom deleters are an abuse of unique_ptr. If you get some malloced region out of some C api, it’s reasonable to put that in a unique_ptr that frees the memory instead of deleteing it.