The C++ Standard moves at a fast pace. Probably, not all developers caught up with C++11/14 yet and recently we got C++17. Now it’ time to prepare C++20!
A few weeks ago The C++ Committee had an official ISO meeting in Jacksonville, FL (12-17 March 2018) where they worked hard on the new specification.
Besides many significant things that were discussed at the meeting like modules, concepts, ranges, The C++ Committee accepted one hugely anticipated feature: deprecation of raw pointers!
Intro
If you’d like to read about all of the changes that the Committee did for C++20 you can check various trip reports that appeared recently. For example:
- Botond Ballo - Trip Report: C++ Standards Meeting in Jacksonville, March 2018
- CppCast: Jacksonville Trip Report with Patrice Roy
- PL22.16/WG21 draft agenda: 12-17 March 2018, Jacksonville, FL, US
Honestly, I rolled my eyes when I saw the proposal of removing raw pointers! Such task looks so complicated! How do they plan to implement that? And what about backward compatibility that is one of the primary goals of new language releases?
But then I understood how excellent that move really is.
Just to be clear about the spec:
The plan is to deprecate raw pointers in C++20. So you’ll get a warning from a conformant compiler. Later in C++23 or C++26, raw pointers will be removed from the language. See more details under this link.
Reasoning
How many times were you tracing some bug, probably for long hours, before noticing the main reason was just having an invalid pointer?
Of course, knowing that your pointer is invalid is not as easy as it may sound. Even if you delete ptr;
and set it to nullptr
you’re not safe. A pointer only represents a memory address, so if you assign it to nullptr
, there’s no automatic propagation of that change to all the owners or observers of this pointer.
The pointer-specific issues (memory issues, pointer indirection, unsafe calls or memory access, to name a few) are probably one of the main the most frequent reasons why our language C++ is perceived as hard to use.
Have a look at Rust. They make a lot of efforts to make the language reliable. It’s still a systems programming language, compiled to machine code. But Rust offers many safety checks. You can use raw pointers but in only a few places. And most of the time the language gives you better alternatives.
Ok, ok… but raw pointers are useful in a lot of cases! So let’s have a look what the Committee proposes as alternatives:
Alternatives to raw pointers
Here are the main examples where raw pointers are handy, and what can we use from modern C++ to exchange them.
Avoiding copying / aliasing
One of an obvious reason to use pointers is to hold an address of some object so that you can manipulate it without the need to copy. Especially handy for passing to functions:
voidProcess(GameObject* pObj){
pObj->Generate();
}
Unfortunately, such code is a common "unsafe" place. For example, you often need to check if such input pointer is not null. Otherwise dereferencing an invalid pointer might generate an unexpected crash.
We have a few alternatives here:
- Pass a value - if your object support move semantics then the copy might not cost much
- Pass a smart pointer
- Pass a reference
- For copyable and assignable references you can use
std::reference_wrapper
.
For now, you can also consider using gsl::not_null
which I described in this post: How not_null can improve your code?.
Polymorphism
References and smart pointers will handle polymorphism. So no worries here.
Dynamic memory allocation
In modern C++ you should avoid using explicit new
. You have many tools to simplify that, like std::make_shared
, std::make_unique
. It’s another case where using a raw pointer is not needed.
std::shared_ptr<int[]> ptrArray(newint[N]);// since C++17
Observing other objects
Using raw pointers for observing other objects is probably the main issue that caused the change in the standard. With raw pointers, you are not sure if the pointer is still valid. Therefore there are many cases where you might encounter an access violation error.
By using smart pointers, you can safely avoid many of such issues. For example, with weak_ptr
you can check if the pointer is still alive or not.
void observe(std::weak_ptr<GameObject> pObj)
{
if(auto observePtr = pObj.lock()){
// object is valid
}else{
// invalid
}
}
Nullable objects
Pointers are also used to transfer the information about the results of some operations:
File*Open(){...}
auto f =Open();
if(f)
{
}
Here we have two problems: the same variable is used to store the objects (the file) and also to convey the message if that object is valid or not. With C++17 we have std::optional
that is perfectly suited for that role. It’s far more expressive and safer.
Performance
Safety is not cheap, and sometimes we must give a bit of performance to have more checks and validations. However, in C++, a lot of pointers alternatives offer no runtime cost. For example, unique_ptr
is safe, and decays to almost nothing, to a raw pointer under the hood. Hence, any memory access made by using this pointer is as cheap as a usage of raw pointer.
Accessing a shared_ptr
is also as fast as a raw pointer, but when copying, shared_ptr
needs to manage the control block which involves atomic operations.
Wrap up
From my perspective, the step of removing pointers will give us an entirely new language! C++ will be safer and more straightforward to learn. What’s more, we don’t lose any performance as we have alternatives that are also as close to the metal as raw pointers.
The devil lies in details, and the Committee needs to do a lot of work to make the final specification. Maybe we’ll get some new mechanism for dealing with pointers: like deferred_ptr or even some garbage collection mechanisms?
There’s an excellent presentation from Herb Sutter about “Leak Freedom”, and you can watch it here:
What’s your view on that?
Can you live without raw pointers?