Unique pointers for parameters

Why are the parameters in an audioProcessorValueTreeState defined as unique_pointers? As opposed to being as regular pointers in pluginProcessor.

Can someone provide an example where using unique pointers is helpful in the lifetime of an audio plugin?

My understanding is that it is generally better to use smart pointers since they clean up after themselves automatically. Unique pointers are good because they provide certain guarantees about ownership.
From what I’ve read and watched, I think the question for modern C++ is that you should provide an example to not use unique pointers, and if there isn’t one then it is best to use them over raw pointers or shared pointers.
I’m simplifying, but I think that’s the overall reasoning.

What’s your problem with unique pointers? They are the most straightforward way to deal with heap allocated objects in C++ code. In some particular instances you might need something else, but almost never raw pointers.

Raw pointers are going to give you a headache unless you rigorously manage them yourself - not saying you won’t, but often, especially in complex multi-threaded applications, you can trip over them. Forget to de-allocate, forget the move/share semantic, etc. Subtle bugs can manifest with raw pointers - the kind that should have been dustbin’ed decades ago. That said, use them if you really know what you’re doing. Most of the time, unique_ptr is a better choice, because scope is important in modern execution environments.

unique_ptr’s are a good ‘default’ choice to use, because they’re not reference counted and therefore don’t have the overhead typically associated with shared_ptr’s, and you can be sure that only one object will ‘own’ the pointer at a time - since you may only std::move them when you expect to.

shared_ptr’s are useful when you only use them when you know you need to share them between execution units, and any alloc’s associated with a shared_ptr will be tracked by the C++ runtime.

You use a unique_ptr in the processor, because this guarantees that only your processor object will have access to the objects referenced by them, ensuring the ‘uniqueness’ of accessing them during the life-cycle of your processing execution unit. In the context of audio processing, you really don’t want the APVTStates’ being modified out of context of the processing execution unit. You also don’t want any overhead (shared_ptr’s have a slight ‘cost’ of reference counting) during the processing execution unit, since audio is performance-dependent.

Raw pointers are useful when you need access to something that you won’t manage - you’re not going to be responsible for allocating the data, you just want to access it - to read values, and make ‘quick’ (non-reference counted) modifications in a ‘safe’ unit of execution. If you don’t ‘own’ the object referred to by the raw pointer, and are not ‘responsible’ for its alloc/de-alloc, and know that your execution unit will not be interrupted during the period of accessing the data behind the pointer, then raw pointers are fine to use.

In short, each pointer type has its uses. Its not a dumb question to ask ‘why?’ - you should know, intuitively, why. If you don’t feel like you understand it properly, its good to ask. As you proceed with larger and more sophisticated C++ projects, the good habits you pick up from the JUCE framework will serve you well 


1 Like

Okay so always use smart pointers - haha I have no problem with them I’m just trying to understand. Thank you @ibisum that was a very helpful response

So here’s a concrete question - let’s say I want to declare an array of pointers of myWavetable from myEngine. myWavetable has a reference variable _wavetable. We want to declare wavetables in myEngine and pass those to each _wavetable in the myWavetables in the initializer of myEngine.

I saw a way to do it if we want each _wavetable referring to a single wavetable in myEngine, but I’m not sure how to do it if we want to declare an array of different wavetables in myEngine, and then initialize each _wavetable to the corresponding wavetable in myEngine.

Yeah, except AddParameter() wants a raw pointer.
Pretty annoying as we like to keep pointers to every parameter so we can call on them directly, encapsulating them in various plugin-specific ways to simplify code and make everything more efficient. (up to 245 automatable parameters in my current project)

Feels like a case for shared_ptr, but them I’m not really familiar with what happens to the object under the hood, especially as the plugin is destroyed.

Guessing it’s just legacy, and we’re all supposed to use valueTree?

Firstly _ prefixed identifiers are reserved and it’s shoddy practice to use them no matter how unlikely it is that the STL uses _wavetable. It can mess with lints, iirc.

Secondly the question is kind of unclear - what is the ownership relationship between myEngine, myWavetable, and _wavetable? And what are their actual types?

Ideally, state should have a single owner. If it must have multiple owners than the answer is shared_ptr but this is usually pretty rare. It’s often just easier/safer to deeply copy the data where needed.

It’s also unclear as to how this relates to parameters - parameters are an abstraction that don’t contain a direct link to the state within your plugin/dsp code. They merely get read to make decisions in your logic to decide what to do.

It would help if you could describe at a high level what your problem is and what your data model looks like in pseudocode instead of talking about variables, since this is really a question about types.

Okay let’s say i have pluginProcessor and myLFO. In pluginProcessor I want to initialize an array of say 10 different instances of myLFO.

myLFO has a reference data structure (say an audioBuffer) that represents the waveform of the LFO (sine, saw, etc), which we want to initialize and edit in pluginProcessor. (Side note, perhaps passing it by reference isn’t the best solution, but I figured it’d be cheaper then recopying the audioBuffer each time we make a change).

Say I initialize an array of 10 audioBuffers in pluginProcessor, and a vector of unique ptrs to myLFO in pluginProcessor

How should I intialize the vector of LFOs and intialize their data structures to the 10 audioBuffers? Since its by reference, i need to do this in the intializer.

This is essentially the structure you’re talking about, correct?

class Processor 
{
private:
    std::vector<std::unique_ptr<Waveform>> waveforms;
    std::vector<std::unique_ptr<LFO>>      lfos;
};

class LFO
{
private:
    Waveform& waveform;
};

In this case, it’s ok to use a raw pointer instead of a reference so long as you guarantee that no LFO instance tries to dereference that pointer after the pointed-to Waveform goes out of scope (if everything is static, you’re fine, if the waveforms can be deleted from the vector, you have problems).

One way to enforce these guarantees is shared_ptr instead of unique_ptr. However I would consider rearchitecting this such that LFO uses an index into the list of available waveforms instead of a pointer. It costs basically the same and has no lifetime worries, plus you can add explicit bounds checks if you want to be ultra-safe. So something like this

struct Processor {
    std::vector<Waveform>   waveforms;
    std::vector<Oscillator> oscillators;
};

struct Waveform {
    /* ... */
};

struct Oscillator {
    size_t waveform;
    /* ... */
};

Just a heads up that addParameter will take ownership. It is an acient API which they wanted to deprecate at some point but for some reasons it is still there.

From the docs for addParameter():

The parameter object will be managed and deleted automatically by the AudioProcessor when no longer needed.

You will have to pass ownership to the processor one way or the other.

Yes, exactly! To your first block of code.

Thanks for your second block of code. That’d be a clean solution. However, I want the user to be able to add new waveforms, delete waveforms, and edit existing ones, so it won’t be a list of fixed available waveforms.

Pedantic, but this is only true for identifiers in the global namespace, or when the underscore is followed by an uppercase letter (or another underscore).

1 Like

You may need to reconsider that, because the state available to the audio processing code can’t change concurrently with an ongoing process call. So it’s much safer for the processor to assume that the available waveforms are fixed, and when a user changes them, the update happens atomically at the top of the process callback and never changes throughout.

If for example a user adds a new waveform and oscillator but the ongoing processing state holds a pointer into a vector or something, it’s possible that the vector will be relocated in memory and all pointers/references are invalidated and you’ll get a segfault. Using an index into a list that gets atomically swapped at the top of the callback guarantees memory safety.

Essentially, indices are much easier to reason about than pointers/references and you can use a variety of techniques to safely swap in a new list of waveforms.

Thanks for the reply @holy-city!

“at the top of the process callback and never changes throughout” sorry not exactly sure what you are saying. Are you saying to have a listener for the changes made to the wavetable? That’s what I’m currently doing.

Let’s forget about adding and deleting wavetables, and say we just want to let the user edit 10 initial wavetables in real time.

For an example think of LFO tool - there is a waveform you can add points to, adjust the slope, etc. This waveform then modulates the incoming audio in real time (e.g. the volume).

In this setting I don’t think indices are the optimal approach, since the waveform is essentially arbitrary.

Regardless of how you solve this problem, it is undefined behavior to read from the waveforms on the audio thread while the user manipulates them on the main thread through some UI. You need some kind of synchronization - all I’m suggesting is that the LFO state doesn’t literally contain a pointer to some other state that needs to be synchronized, but a simple parameter that makes it trivial to look up once it’s synchronized


1 Like