Looking good. Glad to see that you managed to extend it to do more. Code formatting is done with triple grave by the way.
```
So your code should sit between the triple graves like this.
```
Or you can also indent things 4 spaces. I prefer the graves.
I would advise staying away from new
and delete
. In general, you can normally do without them entirely. This can be done by creating the objects on the stack rather than on the heap. E.G:
Hilo myHilo;
myHilo.fillIn();
std::thread t1(mme, std::ref(stop), myHilo);
To use this code you also have to remove the call to delete
and the assignment to nullptr
. This creates your Hilo object on the stack, which means as soon as you exit the main()
function it will be deallocated for you safely. This is safer because if an exception was thrown after you created your Hilo object pointer p
and before it was deleted, that memory would leak.
This should also make you wonder, "Why did I use a pointer to begin with?" in particular look at this line of code: std::thread t1(mme, std::ref(stop), *p);
This is dereferencing the pointer p
to your Hilo object which is allocated on the heap, and then passing it to your function via the std::thread
constructor. First std::thread
will call Hilo's copy constructor, and then as the object is passed to your function mme
Hilo's move constructor will be called.
I won't go too far into what a copy or move constructor is, but if you make any changes to p
after starting the thread you will notice that they are not observed by the thread running mme
, because the copy constructor has instantiated a complete clone of the object. To demo this, let's write an explicit copy/move constructor for Hilo (right now the compiler is writing one for you).
Hilo();
Hilo(Hilo const &aOther);
Hilo(Hilo &&aOther);
Hilo::Hilo(Hilo const &aOther) :
frec1(aOther.frec1),
frec2(aOther.frec2),
bpm(aOther.bpm),
interval(aOther.interval)
{
cout << "Copy constructor called.\n";
}
Hilo::Hilo(Hilo &&aOther) :
frec1(aOther.frec1),
frec2(aOther.frec2),
bpm(aOther.bpm),
interval(aOther.interval)
{
cout << "Move constructor called.\n";
// Note that std::move is often used here rather than copying, but the members of Hilo are all allocated on the stack if Hilo was, meaning we don't have any pointers to move.
}
Now when I run the program I get:
Enter the frequency: 100
Enter them BPM: 120
Copy constructor called.
Move constructor called.
Move constructor called.
As you can see, the copy constructor was called when the dereference Hilo (*p
) was passed to std::thread, and after that, it was moved twice. Moving as you might imagine is better than copying in general for performance, however, performance is a very complex topic that I can't accurately cover in this post. So, why two moves? I'd put money on the first move taking place within the implementation of std::thread, after that the other move was when mme
was called. mme
takes Hilo by value (no reference qualifier or constness or pointer is specified in the function signature) so this allowed the compiler to simply move it's Hilo into that slot for you. This was possible because at that point (after the copy was made) that object was an rvalue.
So, that was a lot of information. I want to add one more thing however.
What could you do other than creating on the stack and other than calling three constructors for Hilo to get it to that thread? The answer here is in pointers. I did say no use of new
or delete
, but I didn't ban pointers! Specifically smart pointers such as std::shared_ptr
and std::unique_ptr
. In this case it's a shared resource, so you can create a Hilo with a shared pointer as follows:
#include <memory>
auto myHilo = std::make_shared<Hilo>();
From there you can use your regular dereferencing operators (*
or ->
) as per your needs just as you did with p
, however, you do not dereference it to pass the argument to std::thread
. At this point you modify mme
to accept a shared_ptr
as well:
void mme (const bool& st, std::shared_ptr<Hilo> a) {
Now we're cooking with gas. When you create your thread now, no copy or move constructors (for Hilo) will be called. Best of all, shared_ptr
will manage your resource for you so there are no leaks, and you don't have to call delete
. It's almost as easy as Java!
Granted, the shared pointer will instead be copied, and then moved twice just like before. But normally a pointer (even a smart one) is smaller than the object it is pointing to.
Good luck in your future endeavours.