- 3 weeks ago
Programs need resources at runtime. Management of resources in C++ programs is explained. https://www.softprayog.in/programming/resource-management-in-c
Category
🤖
TechTranscript
00:00Hello and welcome to this video. I am Karanesh Johri. This video is about
00:06resource management in C++. A program needs resources like memory, sockets, etc. to do its
00:16work. The resources are limited and need to be released or returned back to the system
00:23after use in a timely manner. If the resources are not released, the program floats, its response
00:33time degrades and it still keeps on asking for more and more resources. The guiding principle
00:40in C++ for preventing resource leaks is RAII, which is resource acquisition is initialization.
00:51In this video, we look at the problem of resource leaks and see how with proper use of RAII
00:59we can prevent this problem. So let's get started with resource management in C++.
01:08Resources. What are these resources that we are talking about? The resources are something
01:15a program acquires from the external world. It comes from the system libraries which in turn
01:23get it from the operating system and ultimately it boils down to something in the hardware.
01:31The bottom line is that a resource must be acquired before use and explicitly released
01:38after it is no longer required. Examples of resources are dynamically allocated memory
01:45on the free store, file handles, mutex locks, network sockets, thread handles, etc.
01:56What does a resource leak look like? Here we have two examples. In the first case,
02:02we have a function named f and inside f we allocate an integer object with the value 10 on the
02:11free store
02:12and we do not release it. Now once function f is over, the pointer ptr is lost. ptr is the
02:19owner of the
02:20integer object and ptr could have released the memory of the integer object but we have not done that and
02:29the
02:29ownership of integer object is lost and we can't release it. Now if the function f is called repeatedly
02:38and the program runs for a long time, we can see the negative results of memory leak in function f.
02:47Similarly, in function g, we open a file with fopen function and we have file pointer fp which points
02:56to a file object to a file object but again we do not close the file fp owns the file
03:03object but the ownership
03:04is lost after g completes and the file object keeps on lying somewhere in the memory but it can't be
03:12accessed.
03:13It is a case of resource leak where the resource is file object. We can easily rectify the problem of
03:22resource leak in the two examples. In case of function f we can add delete ptr so that the integer
03:31object is
03:31deleted before the function f ends. In the second case we can add the fclose function before the closing
03:40brace in function g and the file is closed and there is no resource leak. In case of function f
03:47we could have
03:48used a local variable like i instead of allocating an integer on the free store and there would have
03:56been no problem of resource leak in the first place. The resources acquired in a program are owned by
04:04individual objects. Like we said ptr owns the integer object and fp owns the file object. The only problem
04:14is that if the pointed object is not deleted and the corresponding pointer goes out of scope we have
04:21a resource leak. So the owners ptr and fp might not be doing their job unfillingly. We need a system
04:31where an object takes ownership of a resource and automatically acquires or releases it as necessary.
04:40For example consider a vector std vector int v3104159. Here we use the elements of the vector and associated
04:53pointers etc and is responsible for acquiring memory and also for releasing it so that there is no resource
05:03leak and it happens automatically because all standard library containers follow RAII. Which brings us to the
05:13main idea of this video RAII. What is RAII? What does it mean? RAII stands for resource acquisition
05:25is initialization or in other words we say acquire resource during initialization. Initialization of what?
05:35We have said that a resource has an owner object. It is owned by an object. So what is being
05:42said here is
05:44the owner object should acquire resource during initialization. Now initialization is done in
05:51constructor. So all it means is that the resource should be acquired in the constructor of owner object
06:00and since it is being acquired in the constructor it goes without saying that the resource should be
06:07released in the destructor of the owner object. The advantage is that destructor is automatically called
06:15when the lifetime of the object is over. So the resource is linked to the lifetime of the object
06:21and if we strictly follow the policy of acquire resource in constructor and release it in destructor,
06:28we will not have the resource leak for that resource. Here is an example of a class using RAII,
06:36the file wrapper class. If we use fopen for opening a file we get a file pointer resource. Now if
06:45we open
06:45file multiple times without closing it we have a file pointer resource leak. To prevent that we define
06:53file wrapper class where we open the file in the constructor and close it in the destructor. Now we can
07:01simply make an object of class file wrapper and the file is opened in the constructor. When the file wrapper
07:07object comes to end of life the destructor is called and the file is closed. However the standard library
07:15classes are RAII compliant and so if we use the input string if string class object is and initialize it
07:25with
07:26the file file file 1.txt. The file is opened in the constructor and when the object comes to end
07:33of life
07:34it is closed in the destructor. The most common resource leak is the memory leak and in most cases
07:42it starts with raw pointers. A raw pointer points to an object in memory and the pointer goes out of
07:52scope
07:52and the object is left unattended. The ownership of that object is lost and we have a memory leak.
08:00To prevent this we have smart pointers in C++. Smart pointers embed ownership of the pointed object in
08:11pointers. Smart pointers use RAII to manage the lifetime of pointed objects. This makes acquisition
08:20end release of memory resource automatic and hence no memory leak. There are three varieties of smart
08:29pointers and these are unique PTR, shared PTR and weak PTR. We shall look at each of these in a
08:41little detail.
08:43STD unique PTR is a class template. It is defined in the memory header file. A unique PTR owns the
08:51object it
08:52points to. It has exclusive ownership of the object it points to and it is unique PTR's responsibility to
09:01delete the object when it itself is being deleted. So following RAII the pointed object is created in
09:12the unique PTR's constructor and it is deleted in the destructor. A unique PTR cannot be copied because
09:22copying would invalidate the invariant of exclusive ownership. However moving is allowed because moving
09:31transfers the ownership. The standard library provides the function template for making a unique PTR.
09:39It is std make unique PTR. It is std make unique T parameters which returns an std unique PTR type
09:49T.
09:50So we can write std unique PTR int up equal to std make unique int tan. std make unique int
10:04tan allocates
10:05an integer object initializes it to 10 and returns an std unique PTR up to this object. Now unique PTR
10:17up
10:17owns the integer object. It is responsible for managing its life cycle and eventually deleting it. We can try to
10:27print the value of this object and print the value of this object and it prints 10. A unique PTR
10:32can also
10:33point to an array. So we can have something like this std make unique array of integers n. This allocates
10:45an array of
10:46n integers and the unique PTR up points to an array of integers. Now we can see its use
10:55in an example program. Here we are initializing the elements of the array with the index value and we are
11:04printing the elements of the array. A unique PTR cannot be copied. Having a copy of unique PTR would violate
11:13the class invariant the class invariant of exclusive ownership of the pointed object so the copy constructor
11:21and copy assignment operators are disabled. However, a unique PTR can be moved. Moving transfers the ownership.
11:31For example, std unique PTR up equal to std make unique int 10. This allocates an integer object,
11:43initializes it to 10 and unique PTR up points to it and owns the integer object. Next we say
11:52unit PTR int up1 equal to std move up. We move the pointed object from up to up1.
12:03That is we transfer the ownership of the integer object from up to up1 and we can check it by
12:11printing
12:1410. A function can return a unique PTR. In this example,
12:24the function template mkuptr makes a unique PTR pointing to an array of n integers and the array
12:35elements are initialized to the value of index of each element. The return happens either by a new
12:43operation or by a compiler optimization. Shared PTRs are similar to unique PTRs except that only shape
12:53of the pointed object is shared and not exclusive like unique PTRs. Now unique PTRs cannot be copied
13:02but shared PTRs can be copied. Multiple shared PTRs can point to an object. They share the ownership of the
13:12object.
13:13How do we create a shared PTR? Actually we use the standard library function template std make shared t
13:22and this function creates the required object and returns a shared PTR to it. This function also creates
13:30a control block which keeps a use count the number of shared PTRs pointing to the object. When a shared
13:40PTR points to the object, the use count is incremented by 1. When a shared PTR goes out of scope,
13:48the use count is decremented by 1. When the use count becomes 0, the object is deleted. For example,
13:57we can say std shared PTR int sp equal to std make shared int 10. std make shared int 10
14:11makes an integer
14:12object with value 10 and returns a shared PTR int which is stored in sp. We can print the value
14:22of the
14:22integer object using star sp and it prints 10. Shared PTRs can be copied so they can be passed to
14:33functions
14:33as value parameters. When a shared PTR is passed to a function, the use count of the pointed object
14:42goes up by 1 and the function could execute for a long time. They could be concurrent tasks. The point
14:50is that
14:50use count of the pointed object increments when they are passed to the function and it stays that way
14:58as the function executes. When the function returns, the use count is decremented by 1 and when the use
15:07count becomes 0, the object is deleted. A weak PTR is like a shared PTR except that it does not
15:17change
15:18the use count of the pointed object. That is when a weak PTR points to an object, the use count
15:26does not
15:26increase and also it does not decrease when a weak PTR goes out of scope and weak PTR does not
15:35own the
15:36object. So what's the use of weak PTR? Weak PTR has a very important use that it helps in breaking
15:44circular
15:45references when two shared PTRs point to each other. We will look at the problem of circular references
15:53and how weak PTR circumvents that. We can declare a weak PTR like this std weak PTR type id. We
16:04can
16:04copy a shared PTR to a weak PTR that is if a is a shared PTR we can say for
16:11example std weak PTR
16:15node wa equal to a where a is a shared PTR and wa is a weak PTR and most important
16:24we cannot use a weak
16:26PTR to access the pointed object. We need to create a shared PTR from the weak PTR for that and
16:34we can
16:35do that using the lock function. The difficulty is that while we are doing all this the object might
16:42have got deleted. So after creating a shared PTR from weak PTR we need to check whether the shared
16:50PTR is valid. We can do this like this if auto SPA SPA is the new shared PTR if auto
16:59SPA equal to wa dot lock
17:02wa is the weak PTR if auto SPA equal to wa lock SPA is valid otherwise it is not valid.
17:11Weak PTRs are used to break circular references when two shared PTRs point to each other. In this figure
17:20we have two nodes node uppercase a and node uppercase b. Lowercase a and lowercase b point to nodes a
17:29and b
17:30respectively. Each node has two pointers next and previous and let's say to start with both are shared
17:39PTRs. We have a program which creates these two nodes and sets up next pointer in a and previous pointer
17:48in b. In the main function we create nodes a and b using make shared. This returns shared PTRs lowercase
18:00a
18:00and lowercase b respectively. We print the use count of a and b which prints one each. That is there
18:09is one
18:09shared PTR each pointing to nodes a and b. We set the ids of a and b and the next
18:17and previous pointers.
18:19A is next is set to node b and b is set to node a. Now the use count of
18:27a and b is two each. Next we create
18:30weak PTRs wa and wb. These weak PTRs will help us in observing nodes a and b. Now we do
18:41a a dot reset and b dot reset.
18:43Our expectation is that after this both lowercase a and lowercase b pointers become null pointer
18:52and nodes a and b get deleted. Does that happen? If we print the use count of a and b
18:59it shows zero.
19:01Now earlier we had put weak PTRs wa and wb point to nodes a and b and these help us
19:08in finding
19:08whether nodes a and b are still there or they are deleted. So we check wa dot expired and wb
19:17dot expired
19:18which in effect means are there in shared PTRs pointing to nodes a and b. It prints alive in both
19:27cases.
19:28So we are not able to delete nodes a and b even though we did a a dot reset and
19:34b dot reset. We have
19:35a memory leak situation here. The solution is to change either next or previous pointer of struct node
19:43as weak PTR and we make previous as weak PTR. Since b's previous is a weak PTR it does not
19:51increase
19:52node a's use count. So the statement a dot reset makes a a null pointer and node a's use count
20:01drops to
20:01zero. It was one earlier and with a dot reset it becomes zero. So node a is deleted and after
20:10that
20:10b's use count drops to one and we do b dot reset shared PTR b becomes null pointer and node
20:19b's use count
20:20drops to zero and b is also deleted. And we have seen how useful weak PTRs can be changing previous
20:28pointer
20:29from shared PTR to weak PTR. We are able to solve the problem of circular references when two shared PTRs
20:38point to each other. We have seen the problem of resource leak in C++ programs and how to use RAII
20:50to
20:50avoid this problem. You can find the source code of all examples at SoftBlog website. Here is at your code
21:01and the link is there in the description. If you like this video please share it and subscribe to our
21:10channel. Thanks very much for watching. Take care and stay safe.