When moving from C or early C++ to C++11/C++14, it can be difficult knowing where to start. Here are some minor features to focus on that will improve your programming and make your life easier.
NULL
vs nullptr
I’ll just go ahead and say it: eliminate NULL
from your vocabulary right now. nullptr
is the way forward and provides a typesafe value for you to use with your pointers.
NULL
and 0
are int
type. nullptr
was added to represent an explicit pointer type.
Since functions can be overloaded, using NULL
instead of nullptr
could result in the wrong prototype being used. Consider the following case:
int foo(int x);
int foo(int * x);
With these two function calls:
foo(NULL);
foo(nullptr);
The first call would end up invoking foo(int)
while the second call invokes foo(int*)
.
auto
typing
Types in C++ can be very verbose, especially once templating is involved. The auto
type keyword can be used to infer the proper type.
Let’s consider a std::vector
:
std::vector<int> v;
I’ll often see something like this when reviewing code, which isn’t correct:
uint32_t sz = v.size();
The official return type of the std::vector size()
function is actually std::vector<int>::size_type
. By picking uint32_t
above, there’s now potential for a 64-bit to 32-bit conversion. I bet that behavior is probably not what the author intended! Instead, auto
comes to your rescue to prevent you from having to remember the full type.
auto sz = v.size(); //correct type
Or take this shared pointer example:
//Equivalent functionality
std::shared_ptr<uint32_t> ptr(new uint32_t(0xffbbeeaa));
auto ptr(std::make_shared<uint32_t>(0xffbbeeaa));
Using auto
in your code will help save typing, free you to remember more useful details, and improve type safety.
Better Initialization
There are lots of ways to initialize values and objects in C++11/14:
int x(0);
int y = 0;
int z{0};
int z = {0};
In the above examples, the braces represent what C++11 calls “uniform initialization”:
To address the confusion of multiple initialization syntaxes, as well as the fact that they don’t cover all initialization scenarios, C++11 introduces uniform initialization: a single initialization syntax that can, at least in concept, be used anywhere and express everything.
This uniform initialization syntax now allows the following, which was not possible before C++11:
std::vector<int> v{1, 3, 5};
override
keyword
Declare all virtual functions that you plan to override in derived classes with the override
keyword:
virtual int foo(char x) override;
This will tell the compiler to complain if you are not overriding a function but defining a new one instead. Goodbye virtual headaches!
Scoped Enumerations
When using enumerations, you can run into naming issues pretty easily:
enum Color {black, white, red};
auto white = false; //error - white already in scope
Now you’re able to scope your enums within a class!
enum class Color {black, white, red};
auto white = false; //Color::white scoped under Color
auto color = Color::white;
You can also specify the underlying type of the enumeration:
enum class Color: uint8_t {black, white, red}
Further Reading
- Effective Modern C++
- nullptr
- auto specifier
- aggregate initialization
- override specifier
- C++11 enumerations
Using C++ Without the Heap
Want to use C++, but worried about how much it relies on dynamic memory allocations? Our course provides a hands-on approach for learning a diverse set of patterns, tools, and techniques for writing C++ code that never uses the heap.
Learn More on the Course Page