Polymorphism with owned arrays


#1

Hello, I’m having a bit of trouble understanding owned arrays. For a university assignment I’m currently making a midi sequencer.

I have a base class called Region which has two children sequenceRegion and noteRegion.
Currently if i want to add a region they are created in two different functions in two different classes (arrangeWindow and pianRoll)

ArrangeWindow deals with sequenceRegions and pianoRoll deals with noteRegions.
Because there is a lot of repeated code so I’m making a base class called Window that pianoRoll and arrangeWindow can inherit from.

Here are some examples of the code, with the most important bits left in, this is my first time posting a question and I’m fairly new to Juce so i hope I haven’t been to hard to understand.

void ArrangeWindow::addRegion()
{

	sequenceRegions.add(new SequenceRegion);
    sequenceRegions[createNoteRegionCounter] -> regionNumber = createNoteRegionCounter;        
    sequenceRegions[createNoteRegionCounter] -> setSize(80, 80);
    sequenceRegions[createNoteRegionCounter] -> setTopLeftPosition(mousePositionX, mousePositionY);
    addAndMakeVisible(sequenceRegions[createNoteRegionCounter]);
    sequenceRegions[createNoteRegionCounter] -> setListener(this);

}

void PianoRoll::addRegion()
{
noteregions.add(new NoteRegion);
noteregions[createNoteRegionCounter] -> regionNumber = createNoteRegionCounter;
noteregions[createNoteRegionCounter] -> setSize(currentRegionSize, 14);
noteregions[createNoteRegionCounter] -> setTopLeftPosition(mousePositionX, mousePositionY);
addAndMakeVisible(noteregions[createNoteRegionCounter]);
noteregions[createNoteRegionCounter] -> setListener(this);

}

This all works perfectly but i am now trying to make my window class work for both types of region. Here is a rough attempt, which doesn’t work at all (I’m sure for obvious reasons, but as I’m fairly new to c++ and Juce I’m having a bit of trouble understanding how to achieve what i want)

void Window::addRegion(OwnedArray *region)
{

    if (regionType == 0 )
    {
        region -> add(new SequenceRegion);
    }
   
  else if (regionType == 1)
    {
        region -> add(new NoteRegion);
    }



	region[createRegionCounter].regionNumber = createRegionCounter;         
    region[createRegionCounter] . setSize(80, 80);
    region[createNoteRegionCounter] -> setTopLeftPosition(mousePositionX, mousePositionY);
    addAndMakeVisible(region[createNoteRegionCounter]);
    region[createNoteRegionCounter] -> setListener(this);

}

createRegionCounter keeps track of how many have been created/deleted.

Function call:

ArrangeWindow();

arrangeWidnow.addRegion(sequenceRegions)

Pianoroll();

pianoRoll.addRegion(noteRegions)

I’ve left out a lot of the code but that all works how i want to , my main question is how do i pass a reference to an owned array using polymorphism.

Thank you in advance for anyone that can help!


#2

I think you are trying to understand, how to reference a templated class. This is different from polymorphism:

Polymorphism is a class can be specialised by subclassing, but still be usable via the inherited classes interface.
An example would be the AudioParameter class and it’s subclasses AudioParameterFloat, AudioParameterBoolean etc.

Templating means, that a class is specialised for a certain type of thing. In this case a whole new thing is created by the compiler, which is incompatible to any other thing.
An example is AudioBuffer<float> vs. AudioBuffer<double>. There is no way to cast one into the other.

Therefore when you want to give a reference to an OwnedArray, you must supply the type of the Arrays specialisation, in your case:

void Window::addRegion(OwnedArray<Region>& region) {...}

But if your OwnedArray is a member of the class, there is no need to supply the container. May I suggest another approach:

Region* createRegion (const int regionType) {
    ScopedPointer<Region> newRegion;
    if (regionType == 0 ) {
        newRegion = new SequenceRegion;
    }
    else if (regionType == 1) {
        newRegion = region;
    }
    else {
        return nullptr;
    }

    newRegion->regionNumber = createRegionCounter;         
    newRegion->setSize (80, 80);
    newRegion->setTopLeftPosition (mousePositionX, mousePositionY);
    addAndMakeVisible (newRegion);
    newRegion->setListener (this);
    return myArray.add (newRegion.release());
}

This way you can work on one instance without accessing the array over and over.
The benefit using the ScopedPointer inside the method is, if you return anywhere, the created thing is destroyed and the code will never leak.
Also adding it to the array at the end of the method makes sure, that all initialisations are done before it arrives in the array. that can be handy, if different threads might access the array.
Returning the new created instance allowes you to write several commands in one line.

Hope that helps, good luck…


#3

Thanks for the reply thats exactly what i was after! I haven’t used scoped pointers before but i think i understand most of what is happening in your example apart from the last line.

myArray would either be a SequenceRegion, or a NoteRegion and they are declared in the header like this:

OwnedArray myArray;
OwnedArray myArray2;

I tried to do this below and the last line you showed in your reply, but it comes up with and error saying

"Cannot innitalise a paremeter of type SequenceRegion * with an rvalue of type Region *

if (regionType == 0 )
{
return myArray.add (newRegion.release());
}
else if (regionType == 1 )
{
return myArray.add (newRegion.release());
}

I’m sure im missing something quite small and thank you very much for the help!


#4

You cannot use the OwnedArray like this. Because it is a template class, it needs to be specialised for the type it will contain, so either:

OwnedArray<SequenceRegion> myArray;
OwnedArray<NoteRegion>     myArray2;

This will create an array, that can exactly deal with pointers to the template parameter type, i.e. SequenceArray* in the first example.

But if your Array is specialised for the super type Region like:

OwnedArray<Region> myArray;

You can add any instances inheriting Region. I think you said SequenceRegion and NoteRegion both inherit Region, so you can add them both into the array.
BUT when you access the array’s content, you will get only the super class. So you will need to cast to the specific type like:

Region* anyRegion = myArray [anyIndex];
if (SequenceRegion* sequence = dynamic_cast<SequenceRegion*> (anyRegion)) {
    // do something with your sequence
}
else if (NoteRegion* notes = dynamic_cast<NoteRegion*> (anyRegion)) {
    // do something with your notes
}

About the error message: A SequenceRegion is also a Region, so the compiler has no problem to use a SequenceRegion as Region. But a Region is not necessarily a SequenceRegion, so it won’t allow to set a Pointer to a Region as pointer to a SequenceRegion. The method ( I assume the myArray.add(), where myArray is of type OwnedArray) needs a SequenceRegion…


#5

After looking over your examples a couple of times ive got my code working based on the ideas you suggested, and learnt some pretty cool stuff in process!

Thanks again :slight_smile: