Member initialisation best practices query

Hello folks. I have a fairly simple but lengthy query about class member initialization in C++ in general, guided by practices I’ve seen in the JUCE framework. I will probably work this out eventually, but sometimes it’s nice to get a helpful pointer (cough cough) here and there, if anyone has the time :slight_smile:

So lately I’ve been exploring back references and forward declarations, as a means of passing objects into different places in the architecture, other than where that object was instantiated. A core example, is making an APVTS at Processor-level, and passing both those objects forward into a custom parameter-handler class (for scaling, smoothing, etc) before acting on the audio. Like this…

#include "parameters.h"

class MyAudioProcessor  : public juce::AudioProcessor
{
public:
    using APVTS = juce::AudioProcessorValueTreeState;
    
    MyAudioProcessor();
    ~MyAudioProcessor() override;

    APVTS apvts;
    APVTS& getAPVTS() { return apvts; }

private:
    MyAudioProcessorParameters params { *this, getAPVTS() }; // "Parameters.h"
};

The above seems fine… let’s check “Parameters.h”…

class MyAudioProcessor; // forward declaration...

class MyAudioProcessorParameters
public:
    using APVTS = juce::AudioProcessorValueTreeState;
    /** Constructor. */
    MyAudioProcessorParameters(MyAudioProcessor& p, APVTS& apvts);

private:
    //==========================================================================
    // This reference is provided as a quick way for the wrapper to
    // access the processor object that created it.
    MyAudioProcessor& audioProcessor;
    APVTS& state;
};

Now, let’s say in my Parameters class here, I want to call on something like the audio processor instance’s getTotalNumInputChannels() method to configure some further variables with, somewhere within this object. Perhaps to configure e.g. a stereo pan parameter…

It seems that I can call p.method() in the constructor like this;

    MyAudioProcessorParameters::MyAudioProcessorParameters (MyAudioProcessor& p, APVTS& apvts) : audioProcessor (p), state(apvts)
    {
        p.getTotalNumInputChannels()
        // ...then do something useful with the number of channels
    }

Or, I can call the “same” method anywhere within my object by instead using the local member;

    MyAudioProcessorParameters::MyAudioProcessorParameters (MyAudioProcessor& p, APVTS& apvts) : audioProcessor (p), state(apvts)
    {
        audioProcessor.getTotalNumInputChannels()
        // ...then do something useful with the number of channels
    }

So… which of these is technically more “correct”? At least, with respect to doing things the JUCE way?

My question stems from seeing the JUCE modules pretty much always working with the objects as referenced within the constructor (meaning “&p” and “&apvts” here), but in my own hand-sewn projects, doing this causes spurious assertions and the built plugin usually fails to load at all in my DAW. When I switch instead to calling the referenced members ("&audioProcessor", “&state”, etc) suddenly my codebase builds runs smoothly as expected. Yet, I don’t see the same pattern in any JUCE modules. It’s the opposite!

In this particular example, I’m concerned about correct initialization habits and setting up the audioProcessor and APVTS return methods to be leveraged throughout the design, without sleepwalking into major stability bugs that might not show up until later on. It’d be great to access stuff like isUsingDoublePrecision() and such to enhance design flexibility where possible…

Is there a fault in my implementation, my expectations, or both? :stuck_out_tongue:

Cheers

They are identical. In the constructor body the argument p is valid as well as the member variable audioProcessor, which was assigned in the member initialiser list before entering the constructor body.

The only thing to watch out is the initialisation order of the members. You can access not yet initialised references as well as references that became dangling. Both will result in a crash.

The members are strictly initialised in the order they are declared and destroyed in the opposite order.

The CPP-Core guidelines recommend to mark members that rely on other members and are therefore relying on a certain initialisation order (can’t find the exact rule atm.).

There are more recommendations in this chapter:

1 Like

In terms of functionality they’re the same, but I always prefer the second option because it help you keep track of everywhere where you’re using that reference.

Say one day you want to de-couple your code and so you search your class file for “audioProcessor.” to see where you’re using it. You find no matches, so you assume you’re not using it any more and you remove it from the class and all the constructors. But once you’re done you see that you were using it in the constructor after all (p.getTotalNumInputChannels()) and you have to undo the last 10 minutes of work.

1 Like

+1 - good point

Thank you people! Great answers :slight_smile:

I too prefer to use the locally-named member (option 2/“audioProcessor” etc) as this seems to be accessible from the whole of the .cpp body, whereas “p” is only accessible from the constructor. It’s also easier on the eyes in VS.

I was however quite concerned that I may have been making unintentional copies of the APVTS and/or audio processor.

Very well - I shall proceed according to taste. Thanks again kind folks :slight_smile:

There goes all my follow-up questions, top work - thanks again!