A question for the C++ pros


#1

Hello there,

I think I am missing a main concept of C++ can someone clear me up please ?

I have this

class MyComponentA : public component
{
     public:
     int memberVarA;
     void memberFuncA()
     {
     }
     .....
 }

class MyComponentB : public component
{
     public:
     int memberVarB;
     void memberFuncB()
     {
     }
 
      MyComponentA myAComponent;
     .....
 }

So basically I have two classes derived from component, one instantiating the other. So here is my question:

How can I “tell” ComponentB that component A has changed? How can I for example call ComponentB::memberFuncB()?

I thought this can be solved with defining friends, but that is not the case or I messed that up as well…

Anyone able to enlighten me?

many thanks in advance,

Jens


#2

You can make both inheriting from ChangeBroadcaster and ChangeListener. I would prefer that solution, since ComponentA doesn’t need to know any specifics about ComponentB.

That is problematic, it would create an infinite circle, ComponentA has a ComponentB which has a ComponentA, which has a ComponentB…

If you wanted to use a reference instead, the question would be asked, where the components are stored.

Usually both would be child to a parent:

class ParentComponent;

class ComponentA : public Component
{
public:
    ComponentA (ParentComponent& ownerToUse) : owner (ownerToUse) {}

    void foo()
    {
        owner.getComponentB().bar();
    }
private:
    ParentComponent& owner;
};

class ComponentB : public Component
{
public:
    ComponentB (ParentComponent& ownerToUse) : owner (ownerToUse) {}
    void bar() { DBG ("Thanks for calling foo"); }
private:
    ParentComponent& owner;
};

class ParentComponent : public Component
{
public:
    ComponentA& getComponentA() {return compA; }
    ComponentB& getComponentB() {return compB; }

private:
    ComponentA compA { *this };
    ComponentB compB { *this };
};

Hope that helps…


#3

Thank you very much Daniel,

actually as I have a chain of components, so in my case just passing a pointer to the owner was enough.

Still I wonder what the concept is here. In my case (I am still working on a simple step sequncer to learn Juce) I have a component hierarchy (not really important what they do the names suggest it though):

  • StepIndicator - is owned by a step
  • Step - is owned by a pattern
  • Pattern - is owned by a sequence
  • Sequence - is owned by the editor.

So if I change a step, I want the processor to have the data. At the moment I use a timer in the editor class which only when the sequncer is not plaing updates the processor. Works fine, but no realtime changes.

In the actual Step class I react on a mousedown to toggle the step on and off. What I want to do is at that point change the processors step data, so the step is on or off in playback.

I could pass down the owner from level to level and work my way back upwards:

In Step call an function in owner Pattern which - when called- calls a function in the owning sequence, which then calls a function in the editor.

or i could something like

owner.owner.owner.owner.processor.updateStep()

and have each level have a pointer to its parent so I can access the processor directly.

OR

I could change the timer callback to not only during playback check for changes in the patterns.

Which one is the way to go ? I am looking for best practice here, maybe someone from Juce can drop an “offical” word on it.

The timer will work fine, but as I am using LinkedLists for the elements of the pattern even though it might not be more than 8 times 8 notes in the whole sequence I would like to avoid calling

LinkedListPointer.size() as it says that that might get slow and as it would have to happen in the timer callback. And even if I was using pre allocated arrays, the problem would still be passing the infro up from the bottom level element.

ok, enough talk, hope its understandable

Jens


#4

OK,

after playing around a bit I found that I can not access the owner like this, as I can not pass other then member or base classes in the initialization list. So Daniels example will also not work.

this works:

class ComponentB
{
public:
ComponentB();
~ComponentB();
void aga();

 private:
};

class ComponentA
{
public:
	ComponentA(ComponentB& p);
~ComponentA();

private:
ComponentB& myp;
};


ComponentA::ComponentA(ComponentB& p) : myp(p)
{
myp.aga();
}
ComponentA::~ComponentA()
{
}


ComponentB::ComponentB()
{
	ComponentA aA{ *this };
}

ComponentB::~ComponentB()
{
}

void ComponentB::aga()
{
std::cout << "It works\n";
}


int main()
{
	ComponentB a;
}

This does not work:

class ComponentB : public Component
{
public:
ComponentB();
~ComponentB();
void aga();

 private:

};

class ComponentA : public Component
{
public:
	ComponentA(ComponentB& p);
~ComponentA();

private:
ComponentB& myp;
};


ComponentA::ComponentA(ComponentB& p) : myp(p)
{
myp.aga();
}
ComponentA::~ComponentA()
{
}


ComponentB::ComponentB()
{
	ComponentA aA{ *this };
}

ComponentB::~ComponentB()
{
}

void ComponentB::aga()
{
std::cout << "It fails\n";
}

I get:

https://docs.microsoft.com/de-de/cpp/error-messages/compiler-errors-2/compiler-error-c2614?view=vs-2017

So how am I supposed to use this? Is does compile when I use the base clas in the initalization list, but then I can not access non base members functions…

Am I missing something C++ here or is it the wrong way to compose the UI out of single reusable components?


#5

I feel like you are not showing the line, where the error is.
I think it is referring to an initialiser like here:

ComponentA::ComponentA(ComponentB& p) : myp(p)
{
myp.aga();
}

but myp is a member…

Could it be, that you have no forward declaration of ComponentB, so the reference myp is actually already not correctly compiled?

Please copy the error message including the original text here.


#6

I did…

I throws a C2614 which I linked, you can click on “Auf English lesen” sorry I was not able to get the english message.

It works as long as I do not use derived classes. As soon as I use a derived class it stops working, throwing C2614.

The problem is that in the initalizer for the class I am not allowed to use derived classes.

Try your own code, you will see it does not work.

I am quite sure, this is due to my lack of C++ details on how to access the owning class. Maybe I am completely wrong doing it like this, maybe we should not use hirarchy of components. (even if the tutorial tells us to)

Still I can not belive that no one ever tried to get a change from a derived component up to its owner.


#7

Oh and if someone knows how I can COMFORTABLY paste code in here, I will post the original classes.


#8

Ok, the easy answer first, you can surround your code with three backticks:

    ```
    // this is source code
    int main (int, char**)
    {
        return 0;
    }
    ```

and don’t worry about the language, I am originally German :wink:

About the

private:
    ComponentA compA { *this };
    ComponentB compB { *this };

Sorry about that, but the old fashioned way should work:

ParentComponent::ParentComponent ()
: compA (*this), compB (*this)
{
}

// ---
private:
    ComponentA compA;
    ComponentB compB;

Good luck


#9

I do not have it like this - its components containing components. Yours are parallel.

The Owning class (excerpt):

class JeroSequencerPattern    : public Component
{
public:
	const int patternMaxSteps = 8;
	int patternIndex = -1;
	int patternNumSteps = 4;
	float patternProbability = 1.0;
	JeroSequencerPattern* owner = this;
	Colour patCol = Colours::yellow;
				
	struct patternStep
	{
		int index;
		int pitch;
		int velocity;
		float probability;
		bool active;
		JeroSequencerPattern& owner;

		patternStep(int a, int b, int c, float d, bool e, JeroSequencerPattern& f) : index(a),pitch(b),velocity(c),probability(d),active(e), owner(f)
		{
		}
		JeroSequncerStep pStep{ index, pitch,velocity,probability,active,owner};
		LinkedListPointer<patternStep> nextListItem;
	};

	LinkedListPointer<patternStep> patternSteps;

    JeroSequencerPattern(int pInd, int pStp, float pProb)
    {
		patternIndex = pInd;
		patternNumSteps = pStp;
		patternProbability = pProb;

		for (int i = 0; i < patternNumSteps; ++i)
		{
			patternSteps.append(new patternStep(i,1,1,1.0f,false, *this));
		}

		for (int j = 0; j < patternSteps.size(); j++)
		{
			addAndMakeVisible(patternSteps.operator[](j).get()->pStep);
		}


... the rest is not interesting

Owned Component:

```class JeroSequncerStep    : public Component						
{
public:
	int stepIndex=-1;
	int stepPitch=48;
	int stepVelocity=75;
	int stepBrdrSize = 1;
	float stepProbability=1.0;
	bool stepActive;
	const Colour stepFillNrm = Colours::darkgrey;
	const Colour stepFillMso = Colours::lightgreen;
	const Colour stepFillAct = Colours::orange;
	const Colour stepBrdrNrm = Colours::yellow;
	const Colour stepBrdrHlgt = Colours::pink;
	Component& owner;
	
	JeroSequencerStepIndicator stepIndicator{ stepIndex };
		
    JeroSequncerStep(int index, int pitch, int velocity, float probability,bool active, Component& f) : stepIndex(index), stepPitch(pitch),stepVelocity(velocity),stepProbability(probability),stepActive(active), owner (f)
	{
		addAndMakeVisible(stepIndicator);
    }

In the constructor of the owned class JeroSequencerStep I have a pointer to Component (owner) passed as the sith argument(f)

In this version it all works finde and dandy, I just can not acces any function of the owning class JeroSequnecerPattern if its not in the base class Component. So its of no use. But it compiles fine.

if I change: 

```class JeroSequncerStep    : public Component						
{
public:
	int stepIndex=-1;
	int stepPitch=48;
	int stepVelocity=75;
	int stepBrdrSize = 1;
	float stepProbability=1.0;
	bool stepActive;
	const Colour stepFillNrm = Colours::darkgrey;
	const Colour stepFillMso = Colours::lightgreen;
	const Colour stepFillAct = Colours::orange;
	const Colour stepBrdrNrm = Colours::yellow;
	const Colour stepBrdrHlgt = Colours::pink;
	JeroSequncerPattern& owner;
	
	JeroSequencerStepIndicator stepIndicator{ stepIndex };
		
    JeroSequncerStep(int index, int pitch, int velocity, float probability,bool active, JeroSequencerPatternt& f) : stepIndex(index), stepPitch(pitch),stepVelocity(velocity),stepProbability(probability),stepActive(active), owner (f)
	{
		addAndMakeVisible(stepIndicator);
    }

I get:

https://i.imgur.com/ufd36R9.png

where it denies to compile with C2614, the errors about the f, even if the preceed the error of C2614 are just produced by it.

#10

Ok, I am a little confused… start with the first error. Usually every error has unexpected consequences, so the first error is "f": undeclared identifier, so you have to find out, why the compiler thinks, f is undeclared.

Also a little clarification seems in order, maybe you are aware of it already:

  • There is the concept ownership, that is what object is responsible for the lifetime.
  • A private class is only known inside the nesting class. But it is unrelated to ownership, as well to inheritance.
  • parent Components are the ones, that have a Component as child component added in a separate list, that is used for painting and event handling. Again, it is independent from ownership, parent classes etc.

For the start, you could use all classes on the same level. Maybe that is easier to handle. It is always best to start as simple as possible.


#11
class B; // forward declaration

class A 
{
public: 
 A(B & b) : b(b) {}
  void func();
  B & b;
};

class B
{
...
 A a{*this};
 void funcB() { ... }
};


void A::func() . // implementation after B is declared to avoid compile errors
{
   b->funcB();
}

#12

Oh sorry - just read the rest of this.

But if you have some object and you want to call a function from some source object on some target object you need a pointer to it (or a reference which is essentially a pointer) so you can target->function();.

How you get that pointer is where it gets interesting.

You can do it directly (see my previous example). The target class gets told about the source objects pointer during construction.

Or do it indirectly, see juce::ListenerList<> for an example, or ChangeBroadcaster as Daniel suggests (which is essentially a simplified or wrapped version of ListenerList). In these cases the target class tells the source object about the pointer after it’s constructed by calling addListener.

But which ever what you do it you’ll need that pointer (or reference).


#13

The first error is a followup error as it does not accept f to be “JeroSequencerPattern&” its undeclared … so its a followup as I said.

I will just give up on this and use the timer function instead of a good architecture. That hurts less than having all classses on one level.

Also I now these conecpts. I just used owning and owner for clarity as you did so in your code before.

There must be a way to send a message from the child component up to the parent one. How else would all the buttons and stuff work?

I also checked the listeners instead of passing the parents address but they do not seem to be made for this.

I still cant believe no one ever did or tried this.

Thanks Daniel for all the help, and maybe some of the Juce people can clarify on how this is to be used or done in another way.


#14

Thats exactly what is NOT working :wink: It produces a compiler error C2614 which I linked before. It seems ONLY to work for base classes or class members. (Thats what the error explanation on the web says)

Your example might work as its not derived from Component. I also tried to use a void pointer to get around this, but I am having trouble converting it back…

This cost me a whole day, I would be very disappointed if there will be no official answer…


#15

well, there is a solution, I used this many times. But since I can’t see the full error messages with the actual line in the code, it is hard to give you a definite answer. And the example you gave is not really minimal :wink:

If the class JeroSequncerPattern is actually defined later, you have to forward declare it. A forward declaration for a reference works exactly the same like for a pointer. Simply put:

class JeroSequncerPattern;

before the class, where you want to use the reference.

Hope that helps a bit further.
If not, please write the error message and mark to which line in your example the error points to.


#16

I wrote the error message, not sure what else I should do :wink:

The error points to this line in the JeroSequncerStep class:

JeroSequncerStep(int index, int pitch, int velocity, float probability,bool active, JeroSequencerPatternt&; f) : stepIndex(index), stepPitch(pitch),stepVelocity(velocity),stepProbability(probability),stepActive(active), owner (f)

The error is C2614 as I said. look the MS link I gave you.

I have all my classes in seperate files. The JeroSequncerStep is created out of JeroSequencerPattern and the code for it is included there. How do I forward declare it in this case? I tried before the include, did not work…


#17

I made several empty test Projects, this will only work when I split header and implementation (edit: OR put all these classes in one header)

will test that tomorrow and go to bed now :wink:

Thanks a lot guys :wink: Maybe I will finally get it to work… tomorrow…

Good night !


#18

if that is the line you have miss-spelt JeroSequencerPattern and you need to remove that semicolon! Also I assume owner is a JeroSequencerPattern* so it should be owner (&f)

That’s good practice anyway!


#19

Hey Anthony,

that was just a typo in here cause the system did not want the “&” its not in my original code…

And it is a JeroSequencePattern& so its owner ( p). I get the same Problem if I do it the other way round though (I tried Using JeroSequencePattern* and owner(&p))

In the end Daniel was right, its the missing (or due to header only implementation misplaced) forward declaration of JeroSequencerPattern.

Well at least I learned a lot (especially brushed up on how pointers work) again. Also when redoing the class I can correct my typo in the class name :wink: (JeroSequncerStep instead of JeroSequencerStep) I hope someone else also had a chance of taking something out of this.

So my personal best practices doc will get two entries:

  • Do not use header only implementation
  • Always pass a pointer to the parent, you will most likely need it

I also might actually sit down and do more design before jumping in, its not as fun, but it does not hurt either.

Thank you Daniel, Jimc and Anthony !


#20

Those are both very bad rules of thumb!

If you’ve not learned C++ to the level where you’re confident writing header-only classes then you probably need to do some more basic learning about the language itself rather than just avoid it altogether.

And having pointers to other objects is something you should always to keep to an absolute minimum! The rule you should follow is the exact opposite: always try to reduce coupling between classes, and only have pointers if it’s necessary.