Pointers and References

#1

I’m trying to learn about pointers and references.

//define some int variables	
    int a = 1;
    int b = 2;

// define a list of integers
    std::list<int> myInts{ a, b };

// define references to those integers
	int& aRef = a;
	int& bRef = b;

// define a list of references
	std::list<int&> myIntReferences{ aRef, bRef };

// define pointers to the integer variables
	int* aPointer = &a;
	int* bPointer = &b;

// define a list of integer pointers
	std::list<int*> myIntPointers{ aPointer, bPointer };

So far so good, so I attempt to do the same with, for example, some DrumButton objects…

// define the buttons
	DrumButton bassDrumButton{ "Bass Drum", "36" };
	DrumButton snareDrumButton{ "Snare Drum", "38" };
	DrumButton openHatButton{ "Open Hat", "42" };
	DrumButton closedHatButton{ "Closed Hat", "46" };

/// put them in a list - doesnt work!!
	std::list<DrumButton> myButtonsList({ bassDrumButton, snareDrumButton });

/// try to add them with 'address of' - does work!! eh what??
	std::list<DrumButton> myButtonsList{ &bassDrumButton, &snareDrumButton };

/// ..but adding one more and it fails - 'cannot initialize with with initializer list!?!?   
	std::list<DrumButton> myButtonsList{ &bassDrumButton, &snareDrumButton, &openHadButton };

/// try with pointers and no such problem
	std::list<DrumButton*> buttons = { &bassDrumButton, &snareDrumButton, &openHatButton, &closedHatButton };

So that’s confusing - why on earth could I not add the buttons as ordinary variables - and why is there no error when I add two using the ‘address of’ but adding more is not allowed?

I then tried to explicitly define some DrumButton references and put those in a list of DrumButton references…

	DrumButton& bdRef = bassDrumButton;
	DrumButton& sdRef = snareDrumButton;
	DrumButton& ohRef = openHatButton;
	DrumButton& chRef = closedHatButton;

/// didn't work
	std::list<DrumButton&> myButtons{ bdRef, sdRef, ohRef, chRef };

/// but I could add them one by one like this..
	myButtons.emplace_back(bd);
	myButtons.emplace_back(sd);

Finally, I have been advised to avoid raw pointers as much as possible so I tried to make a list of unique_pointers…

/// first define a couple of DrumButton pointers.. 
	std::unique_ptr<DrumButton> buttonPointer = std::make_unique<DrumButton>();

	std::unique_ptr<DrumButton> anotherButtonPointer = std::make_unique<DrumButton>();

/// Then define a list of DrumButton pointers..
	std::list<std::unique_ptr<DrumButton>> listOfButtonPointers;

// add to the list..
	listOfButtonPointers.push_back(buttonPointer);
	listOfButtonPointers.push_back(anotherButtonPointer);

This seemed to work - but can it be done in a more compact way like list initialization?

0 Likes

#2

It’s a complicated subject over all, but a quick tip for starters : don’t use std::list unless you have a very good reason to. (I haven’t found a reason even once in over 10 years I’ve been doing C++.)

Whether you can put objects as values in container objects like std::list or not, depends on things like if the element class has default constructor, copy constructor etc. (Raw pointers will pretty much always work since they are essentially just integer numbers.)

0 Likes

#3

Really? Wow - what are the preferred data structures for collections in C++? Or is it best not to iterate through collections when you’re performing some repetitive task like ‘for each button, get the name and set the colour’ for example?

0 Likes

#4

Obviously it makes sense to use container objects. std::list just happens to a pretty bad one. Better alternatives are std::vector or Juce’s Array. (Or OwnedArray for storing pointers to objects.)

0 Likes

#5

I have heard that using raw pointers is not advised too - we should prefer unique pointers instead? Is that right as a general approach?

0 Likes

#6

Owning raw pointers should be avoided as much possible because it’s so easy to lose track of what part of the code needs to delete the pointed to objects. But if you store and pass around raw pointers that are not supposed to own the thing they are pointing to, that can be OK and often useful. (But even that should generally be avoided.)

std::unique_ptr should be preferred when possible but they are not always the most convenient to use.

What are you actually trying to do? What kind of a class did the DrumButton end up being? I guess it inherits from Juce’s Component because it is a “button”? You will pretty much need to store those as some kind of pointers into containers, because Component doesn’t work as a value type, by design.

0 Likes

#7

Ah, you remember my DrumPad fiasco! :stuck_out_tongue_winking_eye:

Well, in the end I realized that what I needed made more sense to just inherit from the Juce Component and extend it with a couple of fields. Right now i’m just trying to figure out the best way to store a collection of these buttons so that I can work with them together rather than one at a time - like getting their names and displaying them. So far the only way I managed to do this was using pointers like you say. But yeah, i’m just trying to find the most efficient way to work with groups of items that I will do the same or similar things to or with.

0 Likes

#8

The simplest is to use OwnedArray<Component>, it was written specifically for that purpose.
If you want to go with the latest C++ paradigms, you can also use the std::vector<std::unique_ptr<Component>> you mentioned. Technically there won’t be much difference.
std::list is a special case, it is only advised, if you have to add and remove elements somewhere in the middle very often. And even then, a vector is most likely the better choice:
std::list needs to iterate all elements for lookup, for adding and for removing, making the complexity O(n). A vector has all elements linearly located in memory, so you can access elements in O(1). Only adding an element in the middle involves moving the other elements.

1 Like

#9

I have myself usually used std::vector<std::unique_ptr<MyComponentType>> for owning and holding collections of Juce Components. Some people prefer Juce’s OwnedArray, like OwnedArray<MyComponentType>.

1 Like

#10

Thank you both, really appreciate the help and wisdom :wink:

0 Likes

#11

To initialize and iterate over an OwnedArray :

    m_components = {new MyComponent("test1"),new MyComponent("test2"),new MyComponent("test3")};
    for(int i=0;i<m_components.size();++i)
    {
        addAndMakeVisible(m_components[i]);
        m_components[i]->setBounds(0, 30+30*i, 100, 29);
    }

Where m_components is a class member of the parent component declared as OwnedArray<MyComponent> m_components;

1 Like

#12

Nice :slight_smile:

I did this…

class DrumButton : public TextButton
{
public:

	String noteNumber;

	DrumButton()
	{
		
	}
	DrumButton(const String& name, const String& midiNoteNumber) : TextButton(name)
	{
		noteNumber = midiNoteNumber;
	}
};


class ButtonCollection
{
public:
	ButtonCollection()
	{
		myButtons.add(new DrumButton{ "Bass Drum", "36" });
		myButtons.add(new DrumButton{ "Snare Drum", "38" });
		myButtons.add(new DrumButton{ "Open Hat", "42" });
		myButtons.add(new DrumButton{ "Closed Hat", "46" });

	}
	OwnedArray<DrumButton>& getButtons()
	{
		return myButtons;
	}

private:
	OwnedArray<DrumButton> myButtons;
};


class MainComponent   : public Component
{
public:

    MainComponent();
    ~MainComponent();
    
	void paint (Graphics&) override;
    void resized() override;
	void setUpButton(DrumButton* button);

private:

	ButtonCollection buttonCollection;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

And then…

MainComponent::MainComponent()
{
    setSize (600, 400);

	auto count = buttonCollection.getButtons().size();
	auto window = getLocalBounds();
	auto topSection = window.removeFromTop(window.getHeight() / 2);

	for (DrumButton* button : buttonCollection.getButtons())
	{
		addAndMakeVisible(button);
		setUpButton(button);
		button->setBounds(window.removeFromLeft(window.getWidth() / count));
		count--;
	}
}

void MainComponent::setUpButton(DrumButton* button)
{
	String Name = button->getName();
	button->setButtonText(Name << "(" << button->noteNumber << ")" );
}

Which seems to work. I tried a couple of other ways without the method in the ButtonCollection class…

	OwnedArray<DrumButton>& getButtons()
	{
		return myButtons;
	}

…and they built and ran fine, but crashed as soon as I closed the window with a ‘read access violation’

0 Likes

#13

Weird, it should work fine without the getButtons() method…It’s kind of redundant anyway since you are returning a reference to the private member, so it essentially becomes public anyway.

0 Likes

#14

Well, you’re absolutely right! I removed the method and just made ‘myButtons’ public and it is working. Not sure exeactly what was causing the error when I shut down the window - I think I was doing it like…

class MainComponent   : public Component
{
public:

    MainComponent();
    ~MainComponent();
    
	void paint (Graphics&) override;
    void resized() override;
	void setUpButton(DrumButton* button);

private:

	DrumButton bassDrumButton{ "Bass Drum", "36" };
	DrumButton snareDrumButton{ "Snare Drum", "38" };
	DrumButton openHatButton{ "Open Hat", "42" };
	DrumButton closedHatButton{ "Closed Hat", "46" };

	OwnedArray<DrumButton> myButtons{ &bassDrumButton, &snareDrumButton, &openHatButton, &closedHatButton };

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

and then

auto count = myButtons.size();
auto window = getLocalBounds();
auto topSection = window.removeFromTop(window.getHeight() / 2);

for (DrumButton* button : myButtons)
{
	addAndMakeVisible(button);
	setUpButton(button);
	button->setBounds(window.removeFromLeft(window.getWidth() / count));
	count--;
}

which landed me here when I closed down the window

_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
    #ifdef _DEBUG
    _free_dbg(block, _UNKNOWN_BLOCK);
    #else
    free(block);
    #endif
}
0 Likes

#15

Yeah, you must use OwnedArray only with pointers to objects that will be owned by it. If you declare the objects as values and store pointers to those, things will crash because the value objects are automatically destroyed and the OwnedArray will try deleting them too. So, use OwnedArray only as the single owner of objects. (Because of how the OwnedArray works, you can use “new” when creating the objects and not have the corresponding “delete” which would otherwise usually be required.)

1 Like

#16

Right, got it :wink:

This is teaching me how lucky we are in C# and similar with the automatic garbage collection etc :smile:

0 Likes

#17

If for some (good) reason you need to have the objects available both as values and have pointers to them in an array etc, you can use a non-owning container like Array<DrumButton*> or std::vector<DrumButton*> and store the addresses (raw pointers) of the objects in the container. (But usually isn’t a great idea, you should decide if you are going to have the objects as standalone variables or stored in a container.)

0 Likes

#18

Awesome :slight_smile:

0 Likes

#19

Hello,

I am sure that @Xenakios and @daniel answers are solving your problem in a most correct direction, but as far as I am trying to refresh my knowledge of C++ I have checked your code and I decided to share my findings :stuck_out_tongue:

	// define the buttons
	DrumButton bassDrumButton{ "Bass Drum", "36" };
	DrumButton snareDrumButton{ "Snare Drum", "38" };
	DrumButton openHatButton{ "Open Hat", "42" };
	DrumButton closedHatButton{ "Closed Hat", "46" };

	/// put them in a list - doesnt work!!
	std::list<DrumButton> myButtonsList({ bassDrumButton, snareDrumButton });

To be honest this one works for me (VS2017).

	/// try to add them with 'address of' - does work!! eh what??
	//std::list<DrumButton> myButtonsListTwo{ &bassDrumButton, &snareDrumButton };

This compiles, because std::list has constructor taking two iterators pointing to first and last elements. And pointer to an object behaves as iterator. In this case compilator uses constructor, not initializer list.
See:
https://en.cppreference.com/w/cpp/container/list/list

4) Constructs the container with the contents of the range[first, last)

But it fails in runtime with error: “invalid iterator range”.

To fix this, template argument could be change to pointer:

std::list<DrumButton*> myButtonsListTwo{ &bassDrumButton, &snareDrumButton };

as you did in last example for pointers :slight_smile:

        /// ..but adding one more and it fails - 'cannot initialize with with initializer list!?!?   
	// std::list<DrumButton> myButtonsListThree{ &bassDrumButton, &snareDrumButton, &openHatButton };
  1. There is a typo in openHadButton
  2. After correcting the type it also does not compile, because template argument is not a pointer, so compilator do not try to use initializer list. Also there is no constructor taking three pointers arguments.
    The compilator complains with massage:

E0289 no instance of constructor “std::list<_Ty, _Alloc>::list [with _Ty=DrumButton, _Alloc=std::allocator]” matches the argument list

You already did a fix for this in next example :slight_smile: :

	/// try with pointers and no such problem
	std::list<DrumButton*> buttons = { &bassDrumButton, &snareDrumButton, &openHatButton, &closedHatButton };

Template argument is a pointer, so compilator knows that on initializer list it should expect pointers. It uses initializer list, not a constructor, that is why it works.

As I understand creating a collection of references is illegal in C++, because reference can not be empty.

I hope I did no lied. I played with example code in VS, so I think not.

edit:
In short I wanted to say that " { } ", not necessary means initialization list, but it could also invoke a constructor. :stuck_out_tongue:

Kindly regards,
Mateusz

1 Like

#20

Thanks for the reply! :wink: ) Your insights are appreciated

0 Likes