Using smart pointers with JUCE functions that want raw pointers


#1

I must be missing something. I understand how to create a std::unique_ptr and use it within local scope, and even pass it to my own functions that are designed to accept an argument of said pointer without issue.

The problem I’m running into is when I want to use a smart pointer (private member) with a function or constructor that is looking for a raw pointer. Here is an example:

class SomeJUCEClass
{
private:
	// whatever members

public:
	SomeJUCEClass();
	~SomeJUCEClass();

	void someMethod(AudioBuffer<float> *bufferToFill)
	{
		// do stuff with the buffer
	};
};

class MyClass
{
private:
	// My smart pointer to be used with external class
	std::unique_ptr<AudioBuffer<float>> pAudioBuffer;

	void myMethod()
	{
		SomeJUCEClass objectOfJUCEClass;
		objectOfJUCEClass.someMethod(pAudioBuffer);
	};

public:
	MyClass();
	~MyClass();
};

I realize this may be obvious to veteran C++ programmers fully versed in RAII techniques, but I continue to have difficulties with any approach other than using new in my constructor and delete in my destructor.

Any help in getting past this so I can follow Jules’ golden rule of “never use delete” would be greatly appreciated.


#2
objectOfJUCEClass.someMethod(pAudioBuffer.get());

But why are you using an AudioBuffer via a pointer anyway? It’s itself a manager around pointers/heap allocated memory. You can instantiate it just fine directly like :

AudioBuffer<float> myaudiobuffer;

And your method taking in the buffer could use a reference instead of a pointer :

void someMethod(AudioBuffer<float>& bufferToFill)

However, if you can’t choose the signature yourself for some reason, you could call the function that wants a pointer with your value instantiated buffer by taking its address :

objectOfJUCEClass.someMethod(&myaudiobuffer);

#3

Thanks for the quick reply! Let me work on a more specific (yet brief and concise) example that I can post here. The buffer just happened to be something I was playing with for the first time today, but I’ve run into the same problem with other similar situations where a pointer is absolutely necessary, and I can’t seem to get the smart-pointer-as-member to talk to the external class.


#4

Like I mentioned in the updated reply, if a function whose signature is already determined for you to take in a pointer, you can take an object’s address when doing the call. However, obviously you have to know what the function called is going to do with that address. In some cases you might need a heap allocated object and pass in that pointer. Additionally in some cases you must not use a smart pointer because the function call might want to take ownership of the object. You can learn these things only by reading the documentation carefully, and if that is not clear, by examining the involved code you are calling.


#5

Thanks for the additional information. Taking the address as you suggested might be the way to go. I’ll have to explore that. And yes, these are methods whose signatures I can’t modify, and in some cases (not in JUCE thankfully) the implementation details are in fact hidden from me by the API developer and I can only see function signatures in the header files.


#6

Can I hijack a little and ask about using smart pointers in the context below. One of the few places in my code that uses deletes and would like to know how make these methods fully RAII

AudioBuffer<float> buffer;
AudioBuffer<float> output;

void loadBuffer()
{
    AudioFormatManager formatManager;
    formatManager.registerBasicFormats();
    AudioFormatReader* reader = formatManager.createReaderFor(File("/Users/nammick/Desktop/Dump/test.wav"));
    buffer.setSize((int)reader->numChannels, (int)reader->lengthInSamples);
    reader->read(&buffer, 0,(int)reader->lengthInSamples,0, true,true);
    delete reader;
}

void saveOutput()
{
    File outputFile("/Users/nammick/Desktop/analysis1.wav");
    FileOutputStream * outStream = outputFile.createOutputStream();
    AudioFormat * format = new WavAudioFormat();
    auto * writer = format->createWriterFor(outStream, 44100, output.getNumChannels(), 32,nullptr, 0);
    writer->writeFromAudioSampleBuffer(output, 0, output.getNumSamples());
    delete writer;
    delete format;
}

#7
void loadBuffer()
{
    AudioFormatManager formatManager;
    formatManager.registerBasicFormats();
    std::unique_ptr<AudioFormatReader> reader (formatManager.createReaderFor(File("/Users/nammick/Desktop/Dump/test.wav")));
    // NB: you really must check for a nullptr here
    buffer.setSize((int)reader->numChannels, (int)reader->lengthInSamples);
    reader->read(&buffer, 0,(int)reader->lengthInSamples,0, true,true);
}

void saveOutput()
{
    File outputFile("/Users/nammick/Desktop/analysis1.wav");
    auto outStream = outputFile.createOutputStream();
    // in real code you must check whether the stream is null before continuing
    WavAudioFormat format;
    std::unique_ptr<AudioFormatWriter> writer (format->createWriterFor(outStream, 44100, output.getNumChannels(), 32,nullptr, 0));
    // again, you need add a check for a nullptr here
    writer->writeFromAudioSampleBuffer(output, 0, output.getNumSamples());
}

There are many very old bits of the codebase where if we were writing it today, we’d definitely return a std::unique_ptr (or maybe a ref-counted object) rather than a raw pointer, and these audio stream creation functions are good examples of that. At some point we’ll probably change them so that code like this will become a bit more concise and harder to make a mistake, but a change like that would break everybody’s code, so we’re a bit reluctant to do it.


#8

Also, we have oodles of example code that does this kind of thing, e.g.


#9

K kewl, With regards to nullptr checks - obviously I do that in my projects and handle accordingly I just pulled those too as examples to illustrate concisely what I have tried to get me head round.

I totally understand reluctance in breaking changes. Seems like a place where you may start this is say next major version release. I don’t think devs would mind too much then. Would be good to know the roadmap or timeline on the next major version.


#10

I don’t mind the hijack at all. In fact, your example just pointed me in the right direction for a problem I’m currently trying to solve.

I’m working with a 3rd party API that has a virtual method that needs a raw pointer to an array of floats and then (presumably) writes in audio data one chunk at a time during a loop that looks for EOF based on the remaining number of samples in the source.

I’m looking at the JUCE documentation for AudioFormatWriter, and I don’t see a way to get the current seek position in the output stream, as is present in FileOutputStream. Your example gets the number of samples from the reader, which is returning the sample count for the entire file, I presume. I guess I could write some code that would keep track of each pass and deduce the remaining number of samples to be read, but it seems like there must be a better way to do this.

Can anyone point me to a clear example that shows this approach in action?


#11

Ha! Now I just saw Jules’ example, which I think answers my question. Thanks, all!


#12

I also found myself puzzled by the fact, that the default for createOutputStream() is to append data. I wish there was an option, that you have to specify, if you want to start a new file or if you want to append.

It is worth pointing out, that you will get strange results, if you append using wavFormat.createWriterFor(), since it will start writing a header at the end of the existing file, which a reader usually wouldn’t understand (and probably ignore).


#13

Agreed on this. Seems strange to have lots of code that always does delete then open.


#14

Again, it’s for historic reasons, and again, we could/should change it but it’ll annoy a lot of users as it’d break a lot of code!


#15

So, now that my audio buffer + audio writer issue has been solved by following Jules’ example code, let’s come full circle back to my original question about using the smart pointers. Here is an explicit example of the sort of thing I’ve tried and been unable to accomplish without resorting to raw pointers with new statements in constructor and corresponding delete statements in destructor.

class A
{
public:
	A(const int &valueForMember)
		: member(valueForMember)
	{
		// do stuff in constructor
	};

	~A()
	{
		// do stuff in destructor
	};

private:
	int member;
};

class B
{
public:
	B(/*arguments...*/)
		: pointerToA(nullptr)
	{
		// do stuff in constructor
	};

	~B()
	{
		// do stuff in destructor
	};

	void methodOfB()
	{
		int valueForMemberInA = 113;

		// this gives error "no operator matches these operands"
		pointerToA = new A(valueForMemberInA);
	};

private:
	std::unique_ptr<A> pointerToA;
};

I understand that I could do:

	B(/*arguments...*/)
		: pointerToA(new A(value))

The problem is I don’t know the value for Class A at the time I’m creating the object of Class B, but Class A requires the value and has no default constructor. My solution has been to use the raw pointers in these cases and have Class B create a new Class A object once it has the data it needs to do so.


#16

Not considering the possible other problems, but std::unique_ptr can’t be initialized by assigning a raw pointer into it. You need to do either :

pointerToA = std::unique_ptr<A>(new A(valueForMemberInA));

or

pointerToA = std::make_unique<A>(valueForMemberInA);

The latter is preferable because you don’t need to repeat the name of the type A with that.

It is even more preferable to just avoid using pointers altogether, if at all possible. (It’s not always possible, though.) C++ is not Java where you would need to “new” almost everything.


#17

Ah, brilliant! That was the missing link. (using option #2 make_unique) Now it works. Thank you!!


#18

It’s also possible to do

pointerToA.reset (new A (valueForMemberInA));