Memory Management Operators, Manipulators, Type cast Operator.

What is Memory Management?

Memory management is a process of managing computer memory, assigning the memory space to the programs to improve the overall system performance.

Why is memory management required?

As we know that arrays store the homogeneous data, so most of the time, memory is allocated to the array at the declaration time. Sometimes the situation arises when the exact memory is not determined until runtime. To avoid such a situation, we declare an array with a maximum size, but some memory will be unused. To avoid the wastage of memory, we use the new operator to allocate the memory dynamically at the run time.

Memory Management Operators

In C language (Links to an external site.), we use the malloc() or calloc() functions to allocate the memory dynamically at run time, and free() function is used to deallocate the dynamically allocated memory. C++ (Links to an external site.) also supports these functions, but C++ also defines unary operators such as new and delete to perform the same tasks, i.e., allocating and freeing the memory.

New operator

A new operator is used to create the object while a delete operator is used to delete the object. When the object is created by using the new operator, then the object will exist until we explicitly use the delete operator to delete the object. Therefore, we can say that the lifetime of the object is not related to the block structure of the program.

Syntax

The above syntax is used to create the object using the new operator. In the above syntax, 'pointer_variable' is the name of the pointer variable, 'new' is the operator, and 'data-type' defines the type of the data.

Example 1:

In the above example, 'p' is a pointer of type int.

Example 2:

In the above example, 'q' is a pointer of type float.

In the above case, the declaration of pointers and their assignments are done separately. We can also combine these two statements as follows:

  1. int *p = new int;  
  2. float *q =   new float;  

Assigning a value to the newly created object

Two ways of assigning values to the newly created object:

  • We can assign the value to the newly created object by simply using the assignment operator. In the above case, we have created two pointers 'p' and 'q' of type int and float, respectively. Now, we assign the values as follows:

We assign 45 to the newly created int object and 9.8 to the newly created float object.

  • We can also assign the values by using new operator which can be done as follows:

Let's look at some examples.

  1. int *p = new int(45);  
  2. float *p = new float(9.8);  

How to create a single dimensional array

As we know that new operator is used to create memory space for any data-type or even user-defined data type such as an array, structures, unions, etc., so the syntax for creating a one-dimensional array is given below:

Examples:

In the above statement, we have created an array of type int having a size equal to 8 where p[0] refers first element, p[1] refers the first element, and so on.

Delete operator

When memory is no longer required, then it needs to be deallocated so that the memory can be used for another purpose. This can be achieved by using the delete operator, as shown below:

In the above statement, 'delete' is the operator used to delete the existing object, and 'pointer_variable' is the name of the pointer variable.

In the previous case, we have created two pointers 'p' and 'q' by using the new operator, and can be deleted by using the following statements:

The dynamically allocated array can also be removed from the memory space by using the following syntax:

In the above statement, we need to specify the size that defines the number of elements that are required to be freed. The drawback of this syntax is that we need to remember the size of the array. But, in recent versions of C++, we do not need to mention the size as follows:

Let's understand through a simple example:

  1. #include <iostream>  
  2. using namespace std  
  3. int main()  
  4. {  
  5. int size;  // variable declaration  
  6. int *arr = new int[size];   // creating an array   
  7. cout<<"Enter the size of the array : ";     
  8. std::cin >> size;    //   
  9. cout<<"\nEnter the element : ";  
  10. for(int i=0;i<size;i++)   // for loop  
  11. {  
  12. cin>>arr[i];  
  13. }  
  14. cout<<"\nThe elements that you have entered are :";  
  15. for(int i=0;i<size;i++)    // for loop  
  16. {  
  17. cout<<arr[i]<<",";  
  18. }  
  19. delete arr;  // deleting an existing array.  
  20. return 0;  
  21. }  

In the above code, we have created an array using the new operator. The above program will take the user input for the size of an array at the run time. When the program completes all the operations, then it deletes the object by using the statement delete arr.

Output

C++ Memory Management

Advantages of the new operator

The following are the advantages of the new operator over malloc() function:

  • It does not use the sizeof() operator as it automatically computes the size of the data object.
  • It automatically returns the correct data type pointer, so it does not need to use the typecasting.
  • Like other operators, the new and delete operator can also be overloaded.
  • It also allows you to initialize the data object while creating the memory space for the object.

Manipulators in C++ with Examples:

Manipulators are helping functions that can modify the input/output (Links to an external site.) stream. It does not mean that we change the value of a variable, it only modifies the I/O stream using insertion (<<) and extraction (>>) operators.

For example, if we want to print the hexadecimal value of 100 then we can print it as:

cout<<setbase(16)<<100

Types of Manipulators

There are various types of manipulators:

  1. Manipulators without arguments: The most important manipulators defined by the IOStream library are provided below.
    • endl: It is defined in ostream. It is used to enter a new line and after entering a new line it flushes (i.e. it forces all the output written on the screen or in the file) the output stream.
    • ws: It is defined in istream and is used to ignore the whitespaces in the string sequence.
    • ends: It is also defined in ostream and it inserts a null character into the output stream. It typically works with std::ostrstream, when the associated output buffer needs to be null-terminated to be processed as a C string.
    • flush: It is also defined in ostream and it flushes the output stream, i.e. it forces all the output written on the screen or in the file. Without flush, the output would be the same, but may not appear in real-time.

      Examples:



      #include <iostream>
      #include <istream>
      #include <sstream>
      #include <string>
        
      using namespace std;
        
      int main()
      {
          istringstream str("           Programmer");
          string line;
          // Ignore all the whitespace in string
          // str before the first word.
          getline(str >> std::ws, line);
        
          // you can also write str>>ws
          // After printing the output it will automatically
          // write a new line in the output stream.
          cout << line << endl;
        
          // without flush, the output will be the same.
          cout << "only a test" << flush;
        
          // Use of ends Manipulator
          cout << "\na";
        
          // NULL character will be added in the Output
          cout << "b" << ends;
          cout << "c" << endl;
        
          return 0;
      }
      Output:
      Programmer
      only a test
      abc
      
  2. Manipulators with Arguments: Some of the manipulators are used with the argument like setw (20), setfill (‘*’), and many more. These all are defined in the header file. If we want to use these manipulators then we must include this header file in our program.

    For Example, you can use following manipulators to set minimum width and fill the empty space with any character you want: std::cout << std::setw (6) << std::setfill (’*’);

    • Some important manipulators in <iomanip> are:
      1. setw (val): It is used to set the field width in output operations.
      2. setfill (c): It is used to fill the character ‘c’ on output stream.
      3. setprecision (val): It sets val as the new value for the precision of floating-point values.
      4. setbase(val): It is used to set the numeric base value for numeric values.
      5. setiosflags(flag): It is used to set the format flags specified by parameter mask.
      6. resetiosflags(m): It is used to reset the format flags specified by parameter mask.
    • Some important manipulators in <ios> are:
      1. showpos: It forces to show a positive sign on positive numbers.
      2. noshowpos: It forces not to write a positive sign on positive numbers.
      3. showbase: It indicates the numeric base of numeric values.
      4. uppercase: It forces uppercase letters for numeric values.
      5. nouppercase: It forces lowercase letters for numeric values.
      6. fixed: It uses decimal notation for floating-point values.
      7. scientific: It uses scientific floating-point notation.
      8. hex: Read and write hexadecimal values for integers and it works same as the setbase(16).
      9. dec: Read and write decimal values for integers i.e. setbase(10).
      10. oct: Read and write octal values for integers i.e. setbase(10).
      11. left: It adjusts output to the left.
      12. right: It adjusts output to the right.

    Example:

    #include <iomanip>
    #include <iostream>
    using namespace std;
      
    int main()
    {
        double A = 100;
        double B = 2001.5251;
        double C = 201455.2646;
      
        // We can use setbase(16) here instead of hex
      
        // formatting
        cout << hex << left << showbase << nouppercase;
      
        // actual printed part
        cout << (long long)A << endl;
      
        // We can use dec here instead of setbase(10)
      
        // formatting
        cout << setbase(10) << right << setw(15)
             << setfill('_') << showpos
             << fixed << setprecision(2);
      
        // actual printed part
        cout << B << endl;
      
        // formatting
        cout << scientific << uppercase
             << noshowpos << setprecision(9);
      
        // actual printed part
        cout << C << endl;
    }
    Output:
    0x64
    _______+2001.53
    2.014552646E+05

    static_cast in C++ | Type Casting operators

    A Cast operator is an unary operator which forces one data type to be converted into another data type.
    C++ supports four types of casting:

    1. Static Cast
    2. Dynamic Cast
    3. Const Cast (Links to an external site.)
    4. Reinterpret Cast (Links to an external site.)

    Static Cast: This is the simplest type of cast which can be used. It is a compile time cast.It does things like implicit conversions between types (such as int to float, or pointer to void*), and it can also call explicit conversion functions (or implicit ones).
    For e.g.

    #include <iostream>
    using namespace std;
    int main()
    {
        float f = 3.5;
        int a = f; // this is how you do in C
        int b = static_cast<int>(f);
        cout << b;
    }

    Output:

    3

    Now let’s make a few changes in the code.



    #include <iostream>
    using namespace std;
    int main()
    {
        int a = 10;
        char c = 'a';
      
        // pass at compile time, may fail at run time
        int* q = (int*)&c; 
        int* p = static_cast<int*>(&c);
        return 0;
    }

    If you compile the code, you will get an error:

    [Error] invalid static_cast from type 'char*' to type 'int*'

    This means that even if you think you can some how typecast a particular object int another but its illegal, static_cast will not allow you to do this.

    Lets take another example of converting object to and from a class.

    #include <iostream>
    #include <string>
    using namespace std;
    class Int {
        int x;
      
    public:
        Int(int x_in = 0)
            : x{ x_in }
        {
            cout << "Conversion Ctor called" << endl;
        }
        operator string()
        {
            cout << "Conversion Operator" << endl;
            return to_string(x);
        }
    };
    int main()
    {
        Int obj(3);
        string str = obj;
        obj = 20;
        string str2 = static_cast<string>(obj);
        obj = static_cast<Int>(30);
        return 0;
    }

    Run the above code:

    Conversion Ctor called
    Conversion Operator
    Conversion Ctor called
    Conversion Operator
    Conversion Ctor called

    Lets the try to understand the above output:

    1. When obj is created then constructor is called which in our case is also a Conversion Constructor(For C++14 rules are bit changed).
    2. When you create str out of obj, compiler will not thrown an error as we have defined the Conversion operator.
    3. When you make obj=20, you are actually calling the conversion constructor.
    4. When you make str2 out of static_cast, it is quite similar to string str=obj;, but with a tight type checking.
    5. When you write obj=static_cast<Int>(30), you are converting 30 into Int using static_cast.

    Lets take example which involves Inheritance.

    #include <iostream>
    using namespace std;
    class Base {
    };
    class Derived : public Base {
    };
    int main()
    {
        Derived d1;
        Base* b1 = (Base*)(&d1); // allowed
        Base* b2 = static_cast<Base*>(&d1);
      
        return 0;
    }

    The above code will compile without any error.

    1. We took address of d1 and explicitly casted it into Base and stored it in b1.
    2. We took address of d1 and used static_cast to cast it into Base and stored it in b2.

    As we know static_cast performs a tight type checking, let’s the changed code slightly to see it:

    #include <iostream>
    using namespace std;
    class Base {
    };
    class Derived : private Base { // Inherited private/protected not public
    };
    int main()
    {
        Derived d1;
        Base* b1 = (Base*)(&d1); // allowed
        Base* b2 = static_cast<Base*>(&d1);
        return 0;
    }

    Try to compile the above code, What do you see?? Compilation Error!!!!!!!

    [Error] 'Base' is an inaccessible base of 'Derived'

    The above code will not compile even if you inherit as protected. So to use static_cast, inherit it as public.

    Use static_cast to cast ‘to and from’ void pointer.

    #include <iostream>
    int main()
    {
        int i = 10;
        void* v = static_cast<void*>(&i);
        int* ip = static_cast<int*>(v);
        return 0;
    }