The course is part of this learning path
In this course, we will build on your existing foundational and object-oriented oriented skills and enhance them by looking at templates, the Standard Template Library, and other skills to help you in your builds.
Learning Objectives
- Learn about function templates and class templates
- Learn how to write efficient and excellent code with the data structures and algorithms in the standard template library
- Learn about smart pointers to manage dynamic memory automatically
- Understand friend functions, friend classes, and operator overloading
Intended Audience
- Beginner coders, new to C++
- Developers looking to upskill by adding C++ to their CV
- College students and anyone studying C++
Prerequisites
To get the most out of this course, you should have a basic understanding of the fundamentals of C++.
In the last couple of lectures, we learned about the STL or Standard Template Library and worked with some of the containers, container adapters, iterators, and algorithms that are made available through the STL. In this lecture, we will discuss Smart Pointers, also called Managed Pointers. This type of pointer object was introduced in the C++ 11 Standard. We already went through an entire section on Raw Pointers which are the earlier type of pointers in C++. One downside of these pointers is that you have to manage the memory yourself. In other words, you have to remember to call delete on pointers when you are done with them. Forgetting to deallocate memory that has been allocated with Raw Pointers leads to memory leaks which wastes memory and causes your program to take up more memory than it should, which can lead to performance problems and annoyances for the users. Smart Pointers are objects that manage the dynamically allocated memory associated with them. There are three types of Smart Pointers: unique_ptr, shared_ptr and weak_ptr. A unique pointer owns a dynamically allocated piece of memory, and no two unique_ptrs objects can point to the same allocated memory. This type is the most common type and is appropriate for most users. The memory is deallocated when the unique_ptrs goes out of scope. A shared_ptr shares ownership of dynamically allocated memory. Many pointers of type shared_ptrs can point to the same allocated memory. A count is maintained of all the shared_ptr objects pointing to the same allocated memory, and when the count is zero, the memory is deallocated. A weak_ptr doesn't own the memory that it points to and is only really used in special circumstances related to shared_ptrs. They can point to objects that shared_ptrs are pointing to without increasing the reference count. We won't cover them in this lecture, we'll focus on unique_ptrs since they're by far the most common, and it's relatively easy to figure out and pick up the others when you are comfortable with unique_ptrs. In order to use any of the Smart Pointers, you need the header memory. Let's create a project called SmartPointerFun. Create new project, empty project and then SmartPointerFun. And of course, a main file. We have to include iostream, include memory, and of course, write main. Unique pointer double. I'll just call it myDubPtr and you could create them this way. And then when you use them, you use them just like any other pointer. So, we use the indirection operator, set it to 3.14 and then print out pointer value. Same thing. The reference it. Now let's run it. Alright. 'Debug', 'start without debugging' and we get pointer value is 3.14 which is exactly what we expect. Great. So, not much craziness going on here, right? As we discovered writing the code, you dereferenced the Smart Pointers the same way you dereference a Regular or Raw pointer. The biggest difference is the missing delete statement to deallocate the memory. When you use Smart Pointers, you don't have to manage the memory since it's managed for you. Now, what you see on your screen right here is totally fine. But in the C++ 14 Standard, which was after the 11 Standard of course, the recommendation is that you use the make unique function to make unique pointers. Let's update our code to use the recommended newer syntax. So, right here, instead of doing this, I'm going to say myDubPtr and then equals and then make a function call, make_unique double and then if we had a constructor like a class type and there was a constructor, you would pass in the parameters right there. That will be important for later on, right? Content. So, let's run this. 'Debug', 'start without debugging' and again, pointer value is 3.14 as we expected same output. So, what about an array of unique pointer elements? Let's add some code. So, right here, this time, instead of fully spelling it out, I'm going to put auto. So, I could put unique pointer and then in the brackets put the array type, but I'm going to do it a little differently this time. I'm going to use auto, unique. Notice it's in brackets and then you pass in whatever the argument type is, it'll be array size. I guess I had it in my brain, I was going to create that. We have to actually make it or it can't use it, right? So, set size 5. And of course after we're done with the Dubs pointer down here, let's say for int i equals zero. You know what, I'm going to put that below. Right here, we'll keep those together and for int i equals 0, i less than array size, i++. And I'm going to say my array at i equals i times 2 just like any other array, dynamically allocated or otherwise you use bracket notation. Awesome. Now let's print it out just to prove that we can get the data back out the same way we put it in, myArray at i endl. And of course, let's run it again. 'Debug', 'start without debugging' and we get the values that we expect. Awesome. So, another important thing to know about unique pointers is that because they have ownership of the object, other Smart Pointers won't point to them unless you change the ownership. You do this with the move function. So, let's move the ownership of the double pointer. So, right here underneath our unique pointer for the myDubs right here, I'm going to actually, let's see here. after we're done using it up there, we'll do it here. So, I'll say unique_ptr, double otherPtr. I can't just directly set it to myDubPtr because you'll get an error, right? If you read it carefully, it will say cannot be referenced. It's a deleted function. That's kind of weird but what it's saying is that you can't just assign it directly. You can't do that. You can however do this, move myDubPtr and then we can print out otherPtr like that and let's see what happens. Let's actually print out other pointer is, right there, and check it out. Awesome. So, other pointer is now, 3.14, it has ownership of it now and then we get the data from our array, right? Great. We have successfully transferred ownership from myDubPtr to otherPtr and proven that it does point to the other object that was originally pointed to by myDubPtr. So, before we move on of course, I bet you guess this one, I've got a challenge for you. Create a new project called CarProject. You will create an immutable class car which is quite simple. You can see the class diagram on the screen. In main, create the Car object dynamically using a unique pointer and the make pointer function. You can pass constructor parameters such as red and the number 4 in parentheses just like a regular constructor call. You can call the member functions the same way you do with it using a regular pointer. Remember that you don't have to call delete on the pointer because it's a Smart Pointer and it manages itself. So, pause the video, come back when you're done or if you need some help. How did that go for you? Were you able to complete this project? Let's do it together. So, we'll call it CarProject. Let's do that, so close this and let's create the CarProject, and we will create a main file to get it ready. We will create header file for the Car class and we will create the implementation file for the Car class since we're not using templates. We can just say Car.cpp and of course move these in order that we like. Main, I'll just give a skeleton and include car using namespace std, int main, return 0. We'll go back to Car.h, ifndef CAR_H, define CAR_H, endif. We need the string library, leave a little space there. You can see what we're doing. And the Car class is fairly simple. So, it has a public section and it has a private section. Private data is the color for each car and the number of doors. The public includes the constructor, the getColor( ) const; because we have an immutable class. Same thing with getNumDoors and then of course, we'll go over I'm going to copy this. Go over to Car.cpp, include Car.h, do a little paste. Now we're going to give them bodies. There we go. And of course, we need the scope resolution operator. There we go, Car, getColor, getNumDoors, good. For this one, pretty easy. color equals color and numDoors equals numDoors. And of course we have return color and return numDoors. Now, let's go to main. we need memory too, forgot that. Memory, and we will create a unique pointer to the car because that's what we're supposed to do, right? And we will make it red and have four doors, and then we can use it just like we do any pointer. We use myCarPtr right here, getColor and then we need myCarPtr and we can call getNumDoors, right? Excellent. And of course we want to run it. So, debug, start without debugging and we have red and 4. So, awesome job everyone, using Smart Pointers is pretty much as easy as working with Raw Pointers except they manage their own allocated memory. That's a great feature. So, why would anyone use Raw Pointers now if these new Smart Pointers exist? Well, nothing comes for free in life and programming is no different. There's a little of what we call overhead; meaning extra memory and other resources are used so that the Smart Pointers can keep track of the memory that's been allocated when they delete it. This extra data and effort results again in more memory and processing being used. It's a very small amount so for most applications, it probably won't be a big deal. But if you're writing code to run in a resource restricted environment such as a small device or embedded system of some sort, it would be wiser to manage your own memory and use Raw Pointers. Additionally, many universities and colleges don't really focus as much on the Smart Pointers or even teach them at all in some cases since Raw Pointers are present in most legacy and even many newer systems. So, it's important to know how to use both Raw Pointers and Smart Pointers. In the next lecture, we will discuss a feature in C++ called Friends. Let's get going.
John has a Ph.D. in Computer Science and is a professional software engineer and consultant, as well as a computer science university professor and department chair.