C++: is it possible to instantiate an object with a parametized constructor *on the stack* as a member variable of another class? [SOLVED]

#1

Edit: Anyone reading this in the future, there is a lot of tangential conversation in the posts below, so to summarize the solutions:

  • per @daniel: Simply use curly brackets instead of regular brackets… ReverbAudioSource reverb { &tone, false };
  • per @PluginPenguin: Pass the parameters to the constructor in the initialisation list.

You guys are doing crazy stuff with scoped pointers, unique pointers, smart pointers etc. - all wizardry stuff which I’m sure never results in memory leaks :wink: meanwhile I way prefer to create stuff on the stack wherever possible (partly also because the world of heap allocation scares me)

I’m having trouble with the ReverbAudioSource class, because it needs in its constructor a pointer to an input source, and a bool.

So for example if my app wants to create a ToneGeneratorAudioSource object I create it the good old ‘on the stack’ way ToneGeneratorAudioSource tone;

If I want to use that as the input to a ReverbAudioSource object, this doesn’t work:

ToneGeneratorAudioSource tone;
ReverbAudioSource reverb(&tone, true):

(I’ve tried ReverbAudioSource(&tone, true) reverb; also)

This is my current way of implementing a ToneGeneratorAudioSource and ReverbAudioSource (which I don’t like because when my app becomes more complex, I’ll have some sources on the stack and some will be on the heap, some variables will be passed with the address-of operator, some without and I’d have to mentally keep track of which, ugh):

class AudioApp
{
private:
	AudioDeviceManager dm;
	AudioSourcePlayer player;
	ToneGeneratorAudioSource tone;
	AudioSource* reverb = new ReverbAudioSource(&tone, true);
public:
	AudioApp()
	{
		dm.initialiseWithDefaultDevices(0,1);
		dm.addAudioCallback(&player);
		player.setSource(reverb);
	}
	~AudioApp()
	{
		dm.removeAudioCallback(&player);
		delete reverb;
	}
};

In short, is there a way to have the ReverbAudioSource object instantiated on the stack, (and am I using a ‘correct’ philosophy to want to have it on the stack)

#2

The constructor of any member object of your class needs to be called before the constructor of your class is called. If your member has a default constructor (e.g. needs no parameters for a certain constructor) this default constructor will just be invoked to construct the member object. If however your member object needs parameters for its constructor, you need to pass this parameters to the constructor right before the constructor of your class is called. You do this with the help of the so called initializer list, the syntax is like this for your example class:

class AudioApp
{
public:
    // the initializer list is placed after the parentheses of the constructor and marked with a :
	AudioApp() : reverb (&tone, true)
	{
		dm.initialiseWithDefaultDevices (0,1);
		dm.addAudioCallback             (&player);

		player.setSource (&reverb);
	}
	~AudioApp()
	{
		dm.removeAudioCallback(&player);
	}
private:
	AudioDeviceManager dm;
	AudioSourcePlayer player;
	ToneGeneratorAudioSource tone;
	ReverbAudioSource reverb;
};

Side note: I changed the order of the private and public section. Technically there is nothing wrong with how you did it, but it is quite common to start with the public section, as most times you or someone else reads the code you are more interested in the public interface than in the private internals, so nearly all code you’ll find out there uses this order.

Back to initializers: You can of course initialize multiple members. And you can use arguments passed to your class constructor in the initializer list. Furthermore, this is the only possibility to initialize references as class members. This example might showcase some of the things you can do:

class Foo
{
public:
    Foo (int& someExternalInt) : someString ("Initial Value of external int was: " + juce::String (someExternalInt)),
                                 someIntReference (someExternalInt)
    {
        std::cout << someString << std::endl;
    }

    void incrementExternalInt()
    {
        ++someIntReference;
    }
private:
    juce::String someString;
    int& someIntReference;
}

2 Likes
#3

Yes, that is a good thing to aim for. You can use the default initialisers now:

class AudioApp
{
public:
	AudioApp()
	{
		dm.initialiseWithDefaultDevices(0,1);
		dm.addAudioCallback(&player);
		player.setSource(reverb);
	}
	~AudioApp()
	{
		dm.removeAudioCallback(&player);
	}
private:
	AudioDeviceManager dm;
	AudioSourcePlayer player;
	ToneGeneratorAudioSource tone;
	ReverbAudioSource reverb { &tone, false }; // don't pass on ownership, the class stack will clean up
};

If you want to create it at after the creation, use a std::unique_ptr!

// member
std::unique_ptr<AudioSource> reverb;
// later
reverb.reset (new ReverbAudioSource(&tone, false));
player.setSource (reverb.get());

HTH

2 Likes
#4

Hello PluginPenguin,

Thanks for the tip on initialiser lists. Btw, your Foo example was missing a closing bracket, and for some reason, passing by reference (using the address-of operator) wouldn’t work for me, so here’s the final Foo constructor that does:

Foo(int someExternalInt) : someString("Initial Value of external int was: " + juce::String (someExternalInt)),
                                 someIntReference (someExternalInt)
    {
        std::cout << someString << std::endl;
    }

Regarding the use of private before public. Yes, I wanted to talk about that at some point. To me it seems ridiculous that everyone does it the other way. When I look at someone elses code (or my own that I don’t remember) I scroll into the public: and see code using terms that are completely unknown to me. Since we typically declare variables in private: it’s nice for me to have a heads up that dm is an AudioDeviceManager (for example).

With private: at the top I (almost) always know that the declarations are at the top and with private below public: I have to scroll an uncertain amount in order to find them!

Lastly, have you seen @daniel’s solution, it’s very neat!

#5

Hi Daniel, really good tip with the curly brackets! I will use that… But do you have any idea what is happening when we use curly brackets, and why we have to? Thanks

#6

edit : confusing and misleading post removed.

1 Like
#7

Oh I see - I changed this.

What do you mean by wouldn’t work for you? Changing the constructor as you did it will change the whole idea of the example and will introduce some nasty bug. Let me explain why…

A Quick explanation regarding references: A reference allows you to point to the variable declared somewhere else instead of copying it. Just like a pointer, but with the difference that the reference once created cannot be redirected to another variable and it cannot be null and therefore should always be valid. Now you could use foo in my version like that:

int x = 4;

Foo foo (x);

foo.incrementExternalInt();

std::cout << "x is now " << x << std::endl;

With my version the console output should be

Initial Value of external int was: 4
x is now 5

as I changed x through foo.
Your version works on a copy of x, so the output of your version would be

Initial Value of external int was: 4

!!!!some nasty segmentation fault that will crash the program at runtime

So why will the thing you did crash?
When calling my constructor, int& someExternalInt will point to x. Then int& someIntReference will be set to someExternalInt in the initializer list. Because someExternalInt is a reference itself, in the end someIntReference will point to x. Now the constructor of Foo is called and when it has finished someExternalInt will go out of scope and won’t exist anymore. However x still exists outside the class and you can now modify x from inside your class through someIntReference.

Now with your version int someExternalInt will hold a copy of the value of x. Then int& someIntReference will be set to someExternalInt in the initializer list. Now the constructor of Foo is called and when it has finished someExternalInt will go out of scope and won’t exist anymore. But wait - what happens if you now try to access the variable someIntReference points to? As you see, the variable it points to does not exist anymore and your program will most likely crash.

By the way, the curly bracket version daniel suggested is a bit more tidy and there is nothing wrong with this approach. Technically this should lead to no difference if your member constructor does not rely on parameters passed to the outer class constructor.

One last thing:

If this is the case, the code you are looking at is written bad. A public class interface should have such a clear description and naming convention that you shouldn’t need to look into the internals to understand what it does. And if you need to, member variables should have such clear names that you shouldn’t need to look up their types in the private section too. So e.g. what’s the reason to name an AudioDeviceManager dm? A much better name for the variable would be audioDeviceManager. This way the variable name would make it totally clear what the variable holds. Writing code with this in mind will lead to much much better maintainable code and will save you much time scrolling through old code in order to understand it. If you need further ideas on such concepts just google for the term clean code or maybe get some inspiration from the JUCE Coding Standards

1 Like
#8

Ah, you didn’t include an implementation of Foo, so I was left to my own devices and didn’t include a call to incrementExternalInt()… now I understand what you are trying to show.

In any case, I can get it to compile now, but I had to make another change. In your private member variables, someIntReference doesn’t want to be an int. it wants to be a reference to an int! so: int& someIntReference; (Not sure why int* someIntReference; doesn’t work).

There was also a << missing after std::cout But I understand initialiser lists, passing by reference and the point you wanted to show me :slight_smile: so lets move on from that.

Regarding variable names, I certainly have that Coding Standards in my list of material to get through eventually! And I agree totally with your philosophy on good naming convention. It’s crazy how everyone will say it, and then in the real world it looks like nobody applies it! (I actually got the dm naming convention from an official JUCE seminar!)

Thank you again for your help and level of detail. If I ever make a cent off programming, people like you, Xenakios, Daniel (and everyone else on this forum) are the kind of heroes that never get the real credit !

#9

int* someIntReference; would be a pointer to an int. You can of course use that too, but you need to initialize it with an address of a variable and using the variable pointed to by pointer needs to be written differently. (References are handy because you don’t get the headache of having to dereference the pointer first. But of course references are not the best option in every situation either…)

2 Likes
#10

This is likely because the slides have limited space so short names are used so that code is readable on the slides (there was a talk by Dave Rowland where he pointed out his own terrible names in the example code for just that reason).

My rule of thumb is that “bad” variable names are fine in short scope, where you can see obviously what is going on, but for member variables it’s pretty much a guaranteed awful idea in the long run.

eg. I’ll have lots of stuff like this:

void MyComponent::resized()
{
    auto b = getLocalBounds();
    someChild.setBounds(b.removeFromTop(50));
    someOtherChild.setBounds(b.removeFromTop(100));
}

because it’s easy to see what b is at a glance.

also something like:

... a load of code
   auto r = Rectangle<int>(...complicated rectangle definition...);
   g.drawText("Hello world!", r, Justification::topLeft);
   // never going to use r again
... a load more code

is fine, but something like this isn’t:

   auto a = godKnowsWhat();

   ... so much code that you can't see at a glance what "a" actually is ...

  // now using "a" is a bad idea because you can't see at a glance what it actually is

I think also a lot of example code on the forum will use “bad” naming because
a) the declaration and definition are there visible at a glance
b) the authors can’t be bothered typing out better names :slight_smile:

1 Like
#11

Yup. you are 200% right. I was thinking about this after I wrote that comment.

#12

Just to elaborate about the curly brackets:

From C++11 you can assign your class members a default value, scalar with an equals sign and objects by supplying the constructor arguments in the curly brackets.

struct Foo
{
    int a = 123;
    Foo foo {1, 1, 2, 3, 5};
    Bar bar { foo };
}

Further reading: https://en.cppreference.com/w/cpp/language/data_members#Member_initialization and
https://en.cppreference.com/w/cpp/language/aggregate_initialization

You can still provide a constructor overload, that assigns a different argument to the member constructor, as you would have done before C++11.
So for readability it is nice to have the default values directly where you declare the members, so it is harder to miss one.
And you can use a member declared before as argument to the next member, but not the other way round (which makes sense considering the construction order of members and the reverse destruction order).

1 Like