Summary
In this episode, we're continuing our season on object-oriented programming. We're looking at destructors, which are not used as commonly as constructors. The host explains how destructors are used to release resources when an instance is destroyed and how they should be thin to minimize overhead.
Detailed Notes
In this episode, the host continues the season on object-oriented programming by discussing destructors. Destructors are not used as commonly as constructors, but they play a crucial role in releasing resources when an instance is destroyed. The host explains that the destructor should be thin to minimize overhead and that it should validate the state of the instance before destroying it. He also discusses the importance of using the destructor to verify that you cleaned up after yourself. The host uses relevant examples to illustrate the concept of destructors and their use in object-oriented programming. Overall, the episode provides a clear and concise explanation of destructors and their purpose in object-oriented programming.
Highlights
- Destructors are not used as commonly as constructors.
- Destructors are used to release resources when an instance is destroyed.
- The destructor should be thin to minimize overhead.
- You should validate the state of the instance before destroying it.
- You can use the destructor to verify that you cleaned up after yourself.
Key Takeaways
- Destructors are used to release resources when an instance is destroyed.
- The destructor should be thin to minimize overhead.
- You should validate the state of the instance before destroying it.
- You can use the destructor to verify that you cleaned up after yourself.
- Destructors are an essential part of object-oriented programming.
Practical Lessons
- Use destructors to release resources when an instance is destroyed.
- Keep the destructor thin to minimize overhead.
- Validate the state of the instance before destroying it.
- Use the destructor to verify that you cleaned up after yourself.
Strong Lines
- Destructors are not used as commonly as constructors.
- The destructor should be thin to minimize overhead.
- You should validate the state of the instance before destroying it.
- You can use the destructor to verify that you cleaned up after yourself.
Blog Post Angles
- The importance of using destructors in object-oriented programming.
- The benefits of keeping the destructor thin to minimize overhead.
- The role of the destructor in releasing resources when an instance is destroyed.
- The importance of validating the state of the instance before destroying it.
- The use of the destructor to verify that you cleaned up after yourself.
Keywords
- Destructors
- Object-Oriented Programming
- Resource Management
- Memory Leaks
- Destructor Code
Transcript Text
This is Building Better Developers, the Develop-a-Noor podcast. We will accomplish our goals through sharing experience, improving tech skills, increasing business knowledge, and embracing life. Let's dive into the next episode. Well, hello and welcome back. We're continuing our season where we're looking at object-oriented programming in a practical how to take these theoretical things and apply them in our day-to-day development. This episode, we're going to look at destructors. We have spent some time talking about constructors and initializers and starting up the life of an instance. And now we're going to flip to the other end and look at the death of an instance, the end of an instance. Now destructors are not used as commonly as constructors. And it does sort of make sense because really there's not a whole lot of situations where you need to call a destructor to do cleanup. And essentially that boils down to if you have resources that an instance uses, then the destructor should release those resources. And it actually works similar to what we talked about with a constructor. We had the idea of a constructor that gives us a blank instance and then an initializer that as we talked about is what we really want to do when we grab resources or when we open resources. So for example, if we've got an instance that works with a file, we don't really want to open that file on the constructor. We want to allow the developer an opportunity to have an instance but not yet open a file. And essentially what it does is it gives them a little more control essentially over their ability to do like memory and resource management. If we don't give them that, then that means as soon as they create an instance, then it's going to try to pop a file open. And there's going to be situations where maybe they don't want to do that. Maybe there's some sort of like maybe a flyweight type of approach they want to do or something like that where they need the instance and they want the data, but they don't want the resources necessarily to be touched. In particular, you may have something where you create a bunch of instances and you queue them up to do something with them and you don't want to deal with those resources until you're actually essentially actively working with that instance. There's a lot of situations like that where we want to delay or at least in a sense decouple that resource and how it's being used. Now there's a couple of ways we could do that, but let's just assume that the instance itself has as part of its properties or its attributes, some sort of resource that it's holding on to. And this could be a file, it could be a block of memory, it could be a connection to like a database or a web server or something like that. And the database connection in particular is something we see probably fairly frequently. And as I said, there's a couple of ways you can do that. Some cases you have an instance is passed a connection because the connection you really and the database connection, you want the management of that to be outside of instances because it's bigger than a given instance, than a given class. Generally speaking, sort of the short example of it, if you've got a relational database and you've got a bunch of different tables, you probably roughly have classes that mirror those tables. And so when you're working in one particular class, one particular instance of it, one particular table, then that doesn't necessarily mean that that's all you're going to do with the database. You may be working with multiple tables. So you want to have that one connection instead of either a multiple connections or having to pass that thing around all over the place and have essentially multiple instances sharing a connection. You want to be able to manage that as needed. So if you do need to spawn multiple connections, you can do that. You can pass one instance, one connection, another instance, another connection. I digress a little bit, but I wanted to throw something, sort of an example out there of resource usage. Now when we get to the destructor, ideally, and I'm going to talk about why, ideally you don't actually do much in the destructor. What you're really doing is checking for some stuff and then calling methods. So for example, let's say you've got your database connection that you opened it up, create an instance, and maybe in your initialization, it opens a connection to the database. Thus that thing's open and we don't want to lose an instance and not close the database connection. Now, that may be a case, it could be something that the connection theoretically could live beyond that class. But in this case, we're talking about the class, the instance has a connection to this resource. It is the life of that resource usage is equal to the life of the instance. So we have a constructor and then we call an initialization that ties up that resource. We should mirror that in the destructor side. So we should have some sort of a close or a clean up method that we call that we can do outside of the destructor to shut down our, to release those resources. And then what we do in the destructor is it's going to say, do I have, am I holding open a resource? If so, I'm going to call close. Otherwise I'm done. And the main reason to do that is that in a lot of cases, destructors are tricky. You don't want to get in a situation where you're like closing an instance and you end up throwing an exception because then it becomes a problem. It's like, well, okay, if I get an exception, did I really close the instance? Did I not? Is there additional work that needs to be done? Stuff like that. So instead of essentially handling any exceptions within the destructor, you want to do it outside of that. In this case, if you have a release resources method, you go in the destructor and maybe you have, I guess a pairing with that is something along the lines of our resources open. Some sort of method like that. Maybe it's just one, so you can just check that and just say, hey, do I have a hold on that right now? If I do, close it. If not, then I'm done. I don't care about anything else. All I'm really wanting to do is clean up my resources. You think about it from, I guess, a real world point of view. You may have a, like think of a classroom, a little kid classroom. There's sort of the start of the day, they get toys out and stuff like that. At the end of the day, they have cleanup time and then they're done. Well, really if you're checking the room, that's probably what you're doing is you're really checking those toys. There's a lot of other things that could be in that room. It could be going on in that room, but generally speaking, you have playtime and then clean up from playtime. Then this could be, I guess, at home or anything else or your workspace, your desk at the beginning of the day. You have a get stuff out that I'm using. At the end of the day, put stuff up that I'm not using. If I've already put it up, then I can walk away. care that maybe I've got a cup that's still got some water in it or a trash can that needs to be emptied or something that's not dealing with those specific resources. Instance is the same way. If I've got, and let's just go back to that database example. Let's say I've got an instance that ties to a table. As part of that, I open my database connection. I load data from the database and then I do some work with it. Well, my destructor is probably going to be, in this case, probably the first thing I'm going to do is say, am I in a changed state? Do I need to save my work? This would be even in a destructor, but we want to be able to do it as a method. We would have an isChanged or saveWork that says, am I in a changed state? If I am, then I need to save my work. If not, then I can move on. Similarly, is database connection open or just close? Maybe what you do there is you say, I'm going to close this if it's open. If not, then I just return successful and I move on. I don't really have to close something that's already been closed, which could throw an exception. Let's check. As we should do, generally speaking, before we do work on something, we should check that it's not null or that it's valid, that we're working on something that we should be working on. For example, we have an actual instance and not a null instance of something that we're trying to work with. We have these cleanup methods. That's what the destructor wants to do. It's almost like a validation of an instance, of closing or ending work with an instance. It's like when you leave a room, when your kid, if your parents said, hey, did you turn the light off? Did you clean up after yourself? That's what we're doing here is we're saying in an instructor, we're going to have a check that says, hey, do we need to save work? If so, then we're going to save the work. Do we need to release resources? If so, we need to release the resources. Those other attributes that are part of the instance, for example, let's say it was an address class, an address instance, and it had an address, street address, city, state, zip, things like that. Those are all assigned out. Unless we need to explicitly release the memory from those items, then we don't really care. Those aren't resources, essentially. Those are just values that now just go away. You don't have to set them to null. In some cases you do, though. You may see in a C++, depending on what the library is and stuff like that, I haven't worked in that in a while, but it may be that you go in and you actually allocated space for some of that stuff. Which case, you need to deallocate that space or set values to null and things like that. There are language peculiarities or things that are specific to certain languages that point to whether you have utilized a resource or not. In a similar sense, and one other thing that's sort of a got you that you need to take a look at when you do this is the attributes that you have. Do one of those happen to be an instance of a class that then has a destructor that needs to be dealt with? That's a nice thing about the destructor is that you don't have to actually explicitly go in and close off necessarily all of those things. Check for save, things like that. Generally speaking, you should. You should make sure that you know out a reference to an instance within an instance, because sometimes what happens is the parent instance gets nulled, gets destroyed. But what ends up happening is it doesn't actually release the child attribute and you have an instance that's just now orphaned and floating around in memory, also known as a memory leak. Make sure that you see a create or a use or a new within your class code, within an instance, within the methods that it's working with. There should be somewhere a corresponding close or destructor, set something to null or something like that. It's actually a very logical thing. You start, you initialize. When you're done, you do some cleanup and then you close it or you end it. That's where destructors work. Much like constructors, it's better for them to be thin, essentially, to minimize the overhead of creating and instantiating and destroying instances. But you want to make sure that it does the work that it needs to do. Definitely check for essentially the state of the instance before you just willy nilly go destroying things or closing off resources. Because you may close resources that aren't open and you'll get an exception or something like that. So, something to watch out for. Challenge of the week. When was the last time you wrote a destructor? Do you have one in any of your classes? Because a lot of things just, it's a default. There's one behind the scenes that gets called, but we rarely touch that code and we rarely actually do anything with it. Instead, we probably do have an initializer and a close or a save or something to handle that work. But maybe you need to make sure that you write some destructor code that validates that, that verifies that you did clean up after yourself. Doing so may avoid a memory leak, which is always a pain to debug. That being said, hopefully your day ahead is not full of painful debugging. And as always, you go out there and have yourself a great day, a great week, and we will talk to you next time. Thank you for listening to Building Better Developers, the Developer Noor podcast. For more episodes like this one, you can find us on Apple Podcasts, Stitcher, Amazon, and other podcast venues, or visit our site at developernoor.com. Just a step forward today is still progress. So let's keep moving forward together.