Improving Stability with Modern C++, Part 1 — Getting Started
C++ is a workhorse programming language across the financial industry. At FactSet, it has powered many of our products for almost 30 years. Because our code base predates C++ standardization, we have a lot of legacy code to support. However, we’ve recently completed a major compiler upgrade that enables our workstation developers to start using modern C++ for the first time. While C++ had a reputation for a steep learning curve, we’ve found the new features safer, easy to adopt, and more stable for our clients.
While the C++11 standard recently had its 10th birthday, there are still developers out there using modern C++ for the first time. With that in mind, we’ve prepared a series of short introductory posts on the features of C++11 and beyond. We’re sharing our journey with the wider C++ community in the hopes that others will find it as useful as we have.
Where Should I Begin?
There are two simple changes you can make right away. C++11 introduced the keywords nullptr and override. These new keywords are better than their legacy alternatives and are safe to use everywhere in your code. We’ll go over each one briefly below.
Replace NULL
and 0 with nullptr
NULL
is usually implemented as a macro defined as 0. While it's legal in C++ to compare a raw pointer with 0, it can cause issues because 0 is a valid value for multiple types. Consider the following code. Which overload will be called?
void foo(int) { std::cout << "Called int version"; }
void foo(bool) { std::cout << "Called bool version"; }
void foo(void*) { std::cout << "Called pointer version"; }foo(NULL);
I don’t know, and it’s possible your compiler won’t either. It could select the int
version as if you'd written foo(0)
, or it might fail to compile. However, foo(nullptr)
is guaranteed to call the void*
overload. nullptr
has its own type, std::nullptr_t
, that can implicitly convert to any raw pointer type. It's clearer, more correct, and safe to use anywhere you've used NULL
previously:
foo(nullptr);
void* bar = nullptr;
if (baz != nullptr) { // alternatively, if (baz)
// ...
}
Tag overridden virtual functions with override
Have you ever had this bug?
struct Foo {
virtual void func(int a, std::string b, double c, float d);
};struct Bar : Foo {
void func(int a, std::string b, float c, double d);
};Foo* f = new Bar;
f->func(42, "baz", 3.2f, 7.9); // why wasn't Bar::func called?
The argument lists in both classes don’t match, thus Bar::func
hides Foo::func
instead of overriding it. C++11 added the override
keyword to solve this problem. The compiler will issue an error if you mis-type the arguments, add an extra one, or if the const or reference qualifiers don't match.
struct Bar : Foo {
void func(int a, std::string b, float c, double d) override; // ERROR: argument list doesn't match base class
void func(int a, std::string b, double c, float d) override; // OK
};
As with nullptr
, this is safe to use everywhere you have an overridden member function.
Side note for those with large legacy codebases
Tools such as clang-tidy can make both of these suggested changes for you. Consider using source-aware tooling instead of making these changes by hand.
Where can I learn more?
We’ve based much of our advice at FactSet on the ISO C++ Core Guidelines. See the guidelines for nullptr and override for more details.
What’s next?
Please comment below if you have any questions or suggestions for what topics we should cover next. Future posts we have planned include:
- Range-based for loops
auto
keyword- Memory management
- Rule of Three
- Move semantics
- Perfect forwarding
- Variadic templates
Acknowledgments
Special thanks to all that contributed to this blog post:
Author: Michael Kristofik
Managing Editor: Ralph Kootker
Reviewers: Phani Adusumilli, Jens Maurer, Tim Severijns