Object instantiation C++

#1

I’ve been coding with C# for a while and am a complete noob with C++.

I’d love to know why its fine to instantiate a class with the brace initialization in the header file but not with the ‘classic’ constructor approach?

As an example, inside the header file:

//Code is ok

Label label {"name", "componentName"};

//This is not ok - thinks `label`  is a method

Label label("name", "componentName");

Whereas both ways work in the .cpp files.

0 Likes

#2

If you use

Label label = new Label ("name", "componentName");

the object is allocated on the heap, which you have to either delete, or wrap a smart pointer around it, or else you get a memory leak.
If you do

Label label {"name", "componentName"};

the object is allocated on the stack. You don’t have to free it. It is a “classic” variable if you will. (Without going into too much technical details)

Furthermore a header file serves for declaring a function, a cpp file for implementing said function. You include the header file in other files, in order for the program to know the function signature.

Now if you were a compiler what would you expect from a line like this: String somtehing(...) inside a header file? Yes a function declaration. That’s why you gen en error. If you want to allocate your member variable on the stack and do said thing inside a header file, you need to use braces {} (which can also lead to some problems, because if the class defines an array initialization constructor, it is prefered instead of the standard constructor), or you have to do

Label label = Label ("name", "componentName");

In many cases it is “advised” to use braces for initialization over parantheses, but as I mentioned, this can also cause some problems.

Another question would be whether you really need to initialize these member variables inside the header file, or if it would be better to do so in the constructor of a class for example. But this is a question only you can answer.

EDIT 1:
Just a tip. use
```
code
````
for codeblocks and `code` for inline code fragments to make your posts well formatted.

1 Like

#3

That’s not exactly, what is happening…

There is some information on cppreference about member initialisation.

Originally members were only initialised in the constructor, so any constructor listed the base classes constructor and the members constructors after a colon.

Since C++11 (I believe), you can add in the header a default initialiser, which is supplied in curly braces. This is extremely helpful, especially if you supply multiple constructors, you still only have to write the initialiser once.

4 Likes

#4

Thank you! That helped me quite a bit.

I have been following the ‘getting started’ tutorials and watching some of the Juce conference videos and so far every example has included initializing classes inside the header file (with curly braces).

So, aside from being allocated to different parts of the memory the two objects are the same?

I see why the compiler would think that

Label label(....)

is a function. But then why doesn’t the compiler see

Label label(....)

as a function inside a .cpp file? The syntax is the same.

I also found that it was not possible to ‘new - up’ a class like

Label label = new Label()

inside the header file - meanwhile it was possible to do

Label *label = new Label()

as a pointer to an instance.

0 Likes

#5

Yeah support for brace initialization added in C++11. But the reason for parentheses not working equivalent to braces in this context within a header file still is that the compiler doesn’t know what to do with it (because, as you said, only a brace initializer list is supported).

As I mentioned normally you would initialize any members within the constructor, or with the colon.

I’m sorry if I didn’t quite get everything perfect and 100% right, I just wanted to make it more understandable to a beginner, instead of overwhelming him with cppreference, which can be quite a lot to take in if you’re completely new to c++

0 Likes

#6

Inside a cpp file, you’re implementing a function, or in this case a constructor with

// Example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H

class Example
{
public:
    Example();
    ~Example() = default;
}

// Example.cpp
#include "Example.h"

Example::Example()
{
    Label label (...);
    // Do something with label
}

so I think it should be apparent that in this context, you can’t be defining a function. But this explanation gets really fishy real quick as it isn’t accurately what happens as @daniel mentioned. I could explain it to you with my knowledge about compiler building from university, but that would be too much I think.

About your second question…
new returns a pointer. That’s why you can’t do Label label = new Label();
I know why you’re confused about this as I also worked quite some time with c# but you have to understand the difference between a pointer and no pointer. With new Label() you’re allocating and initializing a new Label object on the heap. This returns a pointer - an address to your object on the heap. You can then either store this “address” (warning not 100% correct) in a raw pointer with Label* label = new Label();, or “put” it inside a smart pointer unique_ptr<Label> label (new Label());, or if you have defined unique_ptr<Label> label before, just label.reset(new Label());

1 Like

#7

No worries, no harm done… but I think a link to the official docs don’t hurt :wink:

That is, because new returns a pointer, not an object. It will work, if you do:

Label* label = new Label();

or, the new fancy way using type deduction:

auto* label = new Label();

But what is important, this raw pointers have their life time not defined. If you don’t call delete, it will stay in memory forever, called leaking.

It is best to use RAII techniques, which is e.g. std::unique_ptr. This is a little object on the stack (or as class member), that is pointing to an object on the heap. Once this little object goes out of scope (block in curly braces closes or the class is destroyed, that it was member of), the pointee is automatically deleted.

This can be done like:

std::unique_ptr<Label> label;
label.reset (new Label ("Foo"));

or in a block on the fly:

auto label = std::make_unique<Label> ("Foo");

Read more on cppreference - std::unique_ptr

1 Like

#8

@Chi79 Also it is preferred to use auto label = make_unique<Label>("Foo"); over unique_ptr<Label> label(new ("Foo")); because it returns a unique_ptr!

Besides the fact that it teaches you to avoid new without knowing for sure that you absolutely need a raw pointer of which you have to take care of (especially because c# and Java teach you that you have to use new in order to create an object. But the meaning in C++ is substantially diefferent), it can also prevent some errors. Imagine for example a function

void function (CustomClass* one, CustomClass* two)
{
    // ....
}

and you pass it

function (new int(10), new int(20));

Now imagine the first allocation succeeds but the second encounters an exception and the allocation never happened. If you now catch said error, you must remember that one could have already been created. Because if you just resume normal execution, you’re probably gonna leak one!

0 Likes

#9

Really appreciate you both taking the time,

It’s apparent that I should work on learning the fundamentals of C++ before trying to write anything serious with JUCE. For all the cosmetic similarities with C# it’s obviously a different beast.

It seems like auto is somewhat akin to var in C#.
The way you describe std::unique_ptr<Label> reminds me of a C# using statement too.

It’s going to be a while before I can realize the midi-sequencer I have in mind methinks :stuck_out_tongue_winking_eye:

0 Likes

#10

Jules collected best practices over time, so this is a good read:
JUCE - Coding standards

With advice about object lifetime, prefer references over pointers, and a lot more useful stuff…

1 Like

#11

You just have to make yourself familiar with the differences. But there is no argument to be made that JUCE can’t serve as a learning platform. Although it also incorporates some things that are of a higher C++ skill level, it is a very well written library and teaches you the C++ best practices from the beginning. You can read about some coding stadard of JUCE here.

But the most important thing is the ability to learn and adopt, which makes for a great developer

EDIT 1:
@daniel Are you serious? :joy: How come that we have exactly the same thought?

2 Likes

#12

So what does make_unique return? not a pointer then?

0 Likes

#13

No it returns a unique_ptr.
The same way make_shared returns a shared_ptr. But I would suggest for you to research about shared pointers some more before using it.

1 Like

#14

it returns a std::unique_ptr templated to the type Label in this case. And this is this thing on the stack, that points to a Label object on the heap memory, just like Label*. The only difference is, that once that std::unique_ptr goes out of scope, it calls delete on that heap object, cleaning up it’s mess.
Apart from that, you can call methods using -> just like on a raw pointer.

1 Like

#15

Yeah as @daniel mentions, you can use the pointer operator -> just like with any raw pointer, because it is overloaded. To access the raw pointer you have to call .get() on the smart pointer. If you don’t want to let the unique_ptr manage the lifetime anymore, you can also do .release() which returns a raw pointer again. But beware! You have to assing this returned pointer to a raw pointer again so it doesn’t get leaked!

auto ptr = make_unique<Label>("Hello");
functionXYZ (ptr.get());

// ptr.release(); -> WRONG!!! Induces leak!
Label* raw_ptr = ptr.release(); // Right! Now you can manage the lifetime
// ptr == nullptr now
delete raw_ptr;

Returning a unique_ptr can lead to some confusion at first. You have to realize that you don’t simply replace every raw pointer with smart pointers! Smart pointers own a pointer -> smart pointers are lifetime managers for you pointers. For example you would mostly use raw pointers as function parameters, as you just want to access them and not OWN them

// void ownFunction (unqiue_ptr<Label> label) { do something with label };
// void function (Label* label) { do something with label };
// ...
auto ptr = make_unique<Label>("Test");
function (ptr.get()); // function does something with our Label
// you can still do something with label here...
ownFunction (move(ptr)); // ownFunction now owns our object
// !!! label is now a nullptr !!!

So if you are using a unique_ptr as a return value, you are telling the user that he has ownership of the object. If you just want to return a pointer to a member object, you would use a raw pointer, as the caller shouldn’t gain ownership of the member

class ClassOne
{
public:
    ClassOne() : label (new Label()) {}
    ~ClassOne();

    Label* getLabel() { return label; }
private:
    unique_ptr<Label> label;
}
1 Like

#16

Although I should mention that a reference is preferred over a raw pointer, if you want to make sure that whatever you need isn’t nullptr, because a reference can’t be null!
If you want read-only access to an object, use a const reference, if you want to modify things, a normal reference

void function (Label& label) { label.setText("Hello"); }

void functionTwo (const Label& label) { label.getText(); }

void functionThree (Label* label)
{
    if (label == nullptr)
    {
        // Do this
    }
    else
    {
        // Do that
    }
}
1 Like