Sometimes, skipping memory cleanup at program termination can save valuable time—especially when working with large data structures—while letting the operating system handle memory release.
There are indeed cases where you do not want to clean up objects you have created with C++. If you know that the program is about to end anyway, cleaning up the data would unnecessarily consume time, and the only resource being occupied is memory, so you can use heap objects to save time.
Every stack object and every global object normally would be removed upon exiting main. But maybe you have created an enormous neural network or a super large search tree. If, for example, it consists of a million small nodes, then cleaning it up can take a few seconds. At the end of the program, the operating system releases all memory en bloc anyway. So you can save yourself the trouble of releasing it just before the program ends.
And this is the case where you are allowed to use new without delete. If the program is going to terminate soon anyway, you can save yourself the trouble of cleaning up memory-only resources.
// https://godbolt.org/z/xE1TWx1ve
#include <map>
#include <memory> // unique_ptr
#include <string>
#include <iostream>
#include <chrono> // time measurement
using std::map; using std::cout; using std::endl; using namespace std::chrono;
struct Node {
std::unique_ptr<int> d_;
Node() : Node{0} { }
explicit Node(int d) : d_{ new int } { *d_ = d; } // also some memory
friend bool operator<(const Node& a, const Node& b) {return *a.d_<*b.d_;}
friend bool operator==(const Node& a, const Node& b) {return *a.d_==*b.d_;}
};
long long millisSince(steady_clock::time_point start) { // Helper for measurement
return duration_cast<milliseconds>(steady_clock::now()-start).count();
}
int main() {
std::unique_ptr<map<int,Node>> huge{ new map<int,Node>{} };
cout << "Building..." << endl;
steady_clock::time_point start = steady_clock::now();
for(int idx=0; idx < 100*1000*1000; ++idx) { // massive amount in the map
(*huge)[idx] = Node{idx};
}
cout << "Done: " << millisSince(start) << " ms" << endl; // timing here
start = steady_clock::now();
huge.reset(); // cleanup here
cout << "End: " << millisSince(start) << " ms" << endl; // timing here
}
To measure how long something takes after exiting main(), you would need timing outside the program. Here, I simulate the cleanup of the map by calling huge.reset(), telling unique_ptr to clean up its charge. You would have done the same without smart pointers with the following:
map<int,Node>> *huge = new map<int,Node>{};
// …
delete huge;
But here you want to avoid raw pointers. In both cases, the rule is this: if you omit delete huge; or write huge.release(); instead of reset(), you save the time needed to clean up the many Node objects. You can find out how much time you save at "End:".
At "Done:", millisSince determines the time in milliseconds required to build the data structure. I chose the number 100*1000*1000 (i.e., 100 million) so that my computer can just handle it:
- Building it takes my computer about 52,000 milliseconds with nine gigabytes of memory.
- Tearing it down takes 6,700 milliseconds.
It's not really much—but on other computers and under different conditions, it might take more time. Whether you need this time-saving feature when exiting is up to you. However, do not let the program design suffer because of it.
In the millisSince() helper function, I use steady_clock from <chrono> for precise time measurement.
Editor’s note: This post has been adapted from a section of the book C++: The Comprehensive Guide by Torsten T. Will.
Comments