About the usage of new and delete, and Stroustrup's advice
It's a great rule. In fact, you can avoid using new
in arguments to smart pointers by using the appropriate make_
functions. For example, instead of:
std::shared_ptr<int> p(new int(5));
You can often do:
auto p = std::make_shared<int>(5);
This also has the benefit of being more exception safe. While a std::make_unique
doesn't yet exist, it is planned to make its way into C++14 (it is already in the working draft). If you want it now, there are some existing implementations.
You can go a step further and even avoid using new
and delete
in constructors and destructors. If you always wrap dynamically allocated objects in smart pointers, even when they're class members, you won't need to manage your own memory at all. See the Rule of Zero. The idea is that it's not the responsibility of your class to implement any form of ownership semantics (SRP) - that's what the smart pointers are for. Then you theoretically never have to write copy/move constructors, copy/move assignment operators or destructors, because the implicitly defined functions will generally do the appropriate thing.
When to use new and when not to, in C++?
You should use new
when you wish an object to remain in existence until you delete
it. If you do not use new
then the object will be destroyed when it goes out of scope. Some examples of this are:
void foo()
{
Point p = Point(0,0);
} // p is now destroyed.
for (...)
{
Point p = Point(0,0);
} // p is destroyed after each loop
Some people will say that the use of new
decides whether your object is on the heap or the stack, but that is only true of variables declared within functions.
In the example below the location of 'p' will be where its containing object, Foo, is allocated. I prefer to call this 'in-place' allocation.
class Foo
{
Point p;
}; // p will be automatically destroyed when foo is.
Allocating (and freeing) objects with the use of new
is far more expensive than if they are allocated in-place so its use should be restricted to where necessary.
A second example of when to allocate via new
is for arrays. You cannot* change the size of an in-place or stack array at run-time so where you need an array of undetermined size it must be allocated via new.
E.g.
void foo(int size)
{
Point* pointArray = new Point[size];
...
delete [] pointArray;
}
(*pre-emptive nitpicking - yes, there are extensions that allow variable sized stack allocations).
Are new and delete still useful in C++14?
While smart pointers are preferable to raw pointers in many cases, there are still lots of use-cases for new
/delete
in C++14.
If you need to write anything that requires in-place construction, for example:
- a memory pool
- an allocator
- a tagged variant
- binary messages to a buffer
you will need to use placement new
and, possibly, delete
. No way around that.
For some containers that you want to write, you may want to use raw pointers for storage.
Even for the standard smart pointers, you will still need new
if you want to use custom deleters since make_unique
and make_shared
don't allow for that.
Why should C++ programmers minimize use of 'new'?
There are two widely-used memory allocation techniques: automatic allocation and dynamic allocation. Commonly, there is a corresponding region of memory for each: the stack and the heap.
Stack
The stack always allocates memory in a sequential fashion. It can do so because it requires you to release the memory in the reverse order (First-In, Last-Out: FILO). This is the memory allocation technique for local variables in many programming languages. It is very, very fast because it requires minimal bookkeeping and the next address to allocate is implicit.
In C++, this is called automatic storage because the storage is claimed automatically at the end of scope. As soon as execution of current code block (delimited using {}
) is completed, memory for all variables in that block is automatically collected. This is also the moment where destructors are invoked to clean up resources.
Heap
The heap allows for a more flexible memory allocation mode. Bookkeeping is more complex and allocation is slower. Because there is no implicit release point, you must release the memory manually, using delete
or delete[]
(free
in C). However, the absence of an implicit release point is the key to the heap's flexibility.
Reasons to use dynamic allocation
Even if using the heap is slower and potentially leads to memory leaks or memory fragmentation, there are perfectly good use cases for dynamic allocation, as it's less limited.
Two key reasons to use dynamic allocation:
You don't know how much memory you need at compile time. For instance, when reading a text file into a string, you usually don't know what size the file has, so you can't decide how much memory to allocate until you run the program.
You want to allocate memory which will persist after leaving the current block. For instance, you may want to write a function
string readfile(string path)
that returns the contents of a file. In this case, even if the stack could hold the entire file contents, you could not return from a function and keep the allocated memory block.
Why dynamic allocation is often unnecessary
In C++ there's a neat construct called a destructor. This mechanism allows you to manage resources by aligning the lifetime of the resource with the lifetime of a variable. This technique is called RAII and is the distinguishing point of C++. It "wraps" resources into objects. std::string
is a perfect example. This snippet:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
actually allocates a variable amount of memory. The std::string
object allocates memory using the heap and releases it in its destructor. In this case, you did not need to manually manage any resources and still got the benefits of dynamic memory allocation.
In particular, it implies that in this snippet:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
there is unneeded dynamic memory allocation. The program requires more typing (!) and introduces the risk of forgetting to deallocate the memory. It does this with no apparent benefit.
Why you should use automatic storage as often as possible
Basically, the last paragraph sums it up. Using automatic storage as often as possible makes your programs:
- faster to type;
- faster when run;
- less prone to memory/resource leaks.
Bonus points
In the referenced question, there are additional concerns. In particular, the following class:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
Is actually a lot more risky to use than the following one:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
The reason is that std::string
properly defines a copy constructor. Consider the following program:
int main ()
{
Line l1;
Line l2 = l1;
}
Using the original version, this program will likely crash, as it uses delete
on the same string twice. Using the modified version, each Line
instance will own its own string instance, each with its own memory and both will be released at the end of the program.
Other notes
Extensive use of RAII is considered a best practice in C++ because of all the reasons above. However, there is an additional benefit which is not immediately obvious. Basically, it's better than the sum of its parts. The whole mechanism composes. It scales.
If you use the Line
class as a building block:
class Table
{
Line borders[4];
};
Then
int main ()
{
Table table;
}
allocates four std::string
instances, four Line
instances, one Table
instance and all the string's contents and everything is freed automagically.
Are 'new' and 'delete' getting deprecated in C++?
Well, for starters, new
/delete
are not getting deprecated.
In your specific case, they're not the only solution, though. What you pick depends on what got hidden under your "do something with array" comment.
Your 2nd example uses a non-standard VLA extension which tries to fit the array on the stack. This has certain limitations - namely limited size and the inability to use this memory after the array goes out of scope. You can't move it out, it will "disappear" after the stack unwinds.
So if your only goal is to do a local computation and then throw the data away, it might actually work fine. However, a more robust approach would be to allocate the memory dynamically, preferrably with std::vector
. That way you get the ability to create space for exactly as many elements as you need basing on a runtime value (which is what we're going for all along), but it will also clean itself up nicely, and you can move it out of this scope if you want to keep the memory in use for later.
Circling back to the beginning, vector
will probably use new
a few layers deeper, but you shouldn't be concerned with that, as the interface it presents is much superior. In that sense, using new
and delete
can be considered discouraged.
Use of new and delete in C++
The fist one allocates a single char. You delete it with:
delete a;
The second one allocates an array of chars. The length you have chosen is a little strange. You deallocate it with:
delete[] a;
Now... I hope you don't think you can put a stringified number in the second a
(something like "123456"
, because you'll need many more bytes. Let's say at least 12 if an int
is 32 bits. There is a funny formula to calculate the minimum length necessary. It's an approximation of the log10 https://stackoverflow.com/a/2338397/613130
To be clear, on my machine sizeof(int)
== 4, but in an int
I can put -2147483648
that is 10 digits plus the -
, so 11 (plus the zero terminator)
Related Topics
Load an Pem Encoded X.509 Certificate into Windows Cryptoapi
How to Use Sendinput Function C++
Why Isn't 'Std::Initializer_List' Defined as a Literal Type
Localtime VS Localtime_S and Appropriate Input Arguments
How to Use an Enum Value in a Switch Statement in C++
Unit Testing of Private Methods
How to Convert Rgb -> Yuv -> Rgb (Both Ways)
Is Assignment Operator '=' Atomic
How to Install/Configure Opencv3.2.0 with C++, Visual Studio 2017
Detect Operator Support with Decltype/Sfinae
Multiple Inheritance + Virtual Function Mess
Using Sfinae for Template Class Specialisation