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
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?
Cheers