Dynamic memory allocation operators

C vs C++ memory allocation

The C++ language provide the operators new and new[] to dynamically allocate memory and the operators delete and delete[] to free it. Unlike the function malloc, which only reserves memory, the operators new and new[] allocate memory for class objects and call their constructors to create them. Similarly, the delete and delete[] operators call the destructor of each object inside the allocated memory space and then free the allocated memory. For that reason, when we allocate memory for class objects, we should (Most of the time) use the operators new, new[], delete and delete[] instead of the functions from the C Standard Library, like malloc and free.

Operators?

Way before, we saw that operators are symbols used to execute operations on variables. The truth is that operators are not necessarily symbols. The only difference between a function and an operator is the way they are called and the number of argument they can use. While functions can take any amount of arguments and that they are given by putting them between parentheses following the name of the function, operators take either one or two arguments, which are called operands, and are usually given by putting them before the operator, to give it as left operand, or after, to give it as right operand. The C++ operators used to handle dynamic memory allocation only take one argument, which is a right operand.

The reason the word usually has been used above, is that the operator [] takes one operand as middle operand rather than left or right, because it is put between the brackets.

The operator new vs new[]

The operator new is used to allocate the memory for, and call the constructor of, one object. The operator new[] is used to allocate the memory for, and create, an array of objects. Objects allocated using the operator new must be deleted (freed) using the operator delete and objects allocated using new[] must be deleted using delete[].

Dynamically allocate objects using the C++ operators

Before seeing how to allocate objects using the new/new[] operators, let us create the class we will be using in the examples below. That class simply contains a constructor that prints a message (Telling us that the constructor has been called) and a destructor that does the same.

AllocObj.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef ALLOCOBJ_HPP #define ALLOCOBJ_HPP #include <iostream> class AllocObj { public: AllocObj(); ~AllocObj(); }; #endif

AllocObj.cpp

1
2
3
4
5
6
7
8
9
10
#include "AllocObj.hpp" AllocObj::AllocObj() { std::cout << "Constructor." << std::endl; } AllocObj::~AllocObj() { std::cout << "Destructor." << std::endl; }

To dynamically allocate an object using the operator new, we must write the keyword new followed by the name of the class to create an object from.

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "AllocObj.hpp" int main() { std::cout << "Before the creation of the object.\n"; // Creates an object of type AllocObj. AllocObj *ptr = new AllocObj; std::cout << "After the creation of the object.\n"; // Deletes the previously created object. delete ptr; std::cout << "After the destruction of the object.\n"; return 0; }

Allocating an array of object using the operator new[] is done the same way as allocating one object with new, except that we must add the number of object to create (The size of the array) between brackets, following the name of the class.

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include "AllocObj.hpp" int main() { // Creates an array of 5 objects of type AllocObj. AllocObj *ptr = new AllocObj[5]; // Deletes the previously created objects. delete[] ptr; return 0; }

Giving arguments to the constructor

By default, the operator new constructs the object using its constructor overload that takes no argument. It is however possible to give arguments to the constructor by adding them, between parentheses, after the name of the class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream> class A { public: A(const std::string & str) : _str(str) { } void print() const { std::cout << _str << std::endl; } private: std::string _str; }; int main() { A *ptr = new A("Argument given to the constructor."); ptr->print(); delete ptr; return 0; }

With the operator new[], we can not give one set of argument that will be used to construct all the objects. We can, however, give the arguments for each object, by putting, after the brackets [] and inside braces {}, the arguments for the constructor of each object, inside braces {} and separated by commas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream> class A { public: A(const std::string & str) : _str(str) { } void print() const { std::cout << _str << std::endl; } private: std::string _str; }; int main() { A *ptr = new A[4]{ {"First object."}, // Argument for the constructor of the first object. {"Second object."}, {"Third object."}, {"Fourth object."} // Argument for the constructor of the fourth object. }; for(unsigned c=0; c < 4; c++) ptr[c].print(); delete[] ptr; return 0; }

Virtual destructor

Like methods, the destructor of a class can be defined as virtual. What is the purpose of that? Imagine that we defined two classes: Weapon and Sword, which inherits from Weapon. If we dynamically allocate an object of type Sword using the operator new and afterward delete it through a pointer of type Weapon, then, if the destructor of Weapon is not virtual, the destructor of the class Weapon will be called instead of the destructor from the class Sword.

Let us code that example:

Weapon.hpp

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef WEAPON_HPP #define WEAPON_HPP #include <iostream> class Weapon { public: ~Weapon(); }; #endif

Weapon.cpp

1
2
3
4
5
6
#include "Weapon.hpp" Weapon::~Weapon() { std::cout << "Destructor of Weapon." << std::endl; }

Sword.hpp

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef SWORD_HPP #define SWORD_HPP #include "Weapon.hpp" class Sword : public Weapon { public: ~Sword(); }; #endif

Sword.cpp

1
2
3
4
5
6
#include "Sword.hpp" Sword::~Sword() { std::cout << "Destructor of Sword." << std::endl; }

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "Sword.hpp" int main() { Sword *sword = new Sword; Weapon *weapon = sword; // Deletes an object of type Sword, pointed by a pointer of type Weapon. delete weapon; return 0; }

By simply adding the keyword virtual before the declaration of the destructor of the class Weapon, the example above would have instead, when the object of type Sword is deleted (Through a pointer of type Weapon), called the destructor of Sword and the string "Destructor of Sword." would have also been printed into the console.

Declaration of the destructor of Weapon as virtual:

virtual ~Weapon();

Now that the destructor of Weapon is virtual, and therefore the destructor of Sword also is, the destructor of Sword is called instead of Weapon. As we know, a destructor call the destructors of its parents, so the destructor from Weapon is called by the destructor of Sword.

Note that if we only defined the destructor of Sword as virtual (And not the destructor of Weapon), it would not have been called (Like in the first example). Why? The reason is that, for it to work, the method/destructor from the class of the pointer the method/destructor is called from must be virtual.

Null pointer as operand to delete/delete[]

If the operators delete and delete[] receive, as operand, addresses that do not contain dynamically allocated objects, the program will crash. This is also true if the operator delete tries to delete an array of objects allocated using new[] and if delete[] is used with an object allocated with new. However, if they receive a null pointer (Address 0), the program do not crash. In that case, they simply do nothing.

C functions vs C++ operators for memory allocation

The functions from the C Standard Library and the C++ operators, used to manage dynamic memory allocation, can not be used together. There is therefore no way to reallocate dynamically allocated memory, the same way as we would do with the function realloc from the C Standard Library, using the C++ operators.