In the previous article, you read about five popular techniques to improve your projects and apply several Modern C++ patterns. Here’s a list of five more things! We’ll go from the override keyword to nullptr, scoped enums, and more. All techniques are super-easy with Visual Assist!
Table of Contents
1. Override
Let’s have a look at the following code with a simple class hierarchy:
using BmpData = std::vector<int>; class BaseBrush { public: virtual ~BaseBrush() = default; virtual bool Generate() = 0; virtual BmpData ApplyFilter(const std::string&) { return BmpData{}; } }; class PBrush : public BaseBrush { public: PBrush() = default; bool Generate() { return true; } BmpData ApplyFilter(const std::string& filterName) { std::cout << "applying filter: " << filterName; return BmpData{}; } private: BmpData m_data; };
When you run VA code inspections, you’ll immediately see that it complains about not using the override keyword on those two virtual methods.
In short, the override keyword allows us to explicitly specify that a given member function overrides a function from a base class. It’s a new keyword available since C++11.
As you might already know, you can go to VA Code Inspection Results and apply the fixes automatically. You’ll get the following code:
class PBrush : public BaseBrush { public: PBrush() = default; bool Generate() override; BmpData ApplyFilter(const std::string& filterName) override; private: BmpData m_data; };
What are the benefits of using override?
The most significant advantage is that we can now easily catch mismatches between the virtual base function and its override. When you have even a slight difference in the declaration, then the virtual polymorphism will might not work.
Another critical point is code expressiveness. With override, it’s effortless to read what the function is supposed to do.
And another one is being more modern as this keyword is also available in other languages like C#, Visual Basic, Java, and Delphi.
2. nullptr
When I work with legacy code, my Visual Assist Code Inspection Result is often filled with lots of the following items:
This often happens with code like:
if (pInput == NULL) { LOG(Error, "input is null!") return; }
or
pObject->Generate("image.bmp", NULL, NULL, 32);
Why does Visual Assist complain about the code? It’s because NULL is just a define and means only 0, so it doesn’t mean a null pointer, but it means 0. This is also a problem when you have code like:
int func(int param); int func(float* data); if you call: func(NULL);
You could expect that the function with the pointer should be called, but it’s not. That’s why it’s often a guideline in C or early C++ that suggests not making function overrides with pointers and integral types.
The solution? Just use nullptr from C++11.
nullptr is not 0, but it has a distinct type nullptr_t.
Now when you write:
func(nullptr);
you can expect the proper function invocation. The version with func(float* data) will be invoked.
Not to mention that nullptr is a separate keyword in C++, so it stands out from the regular code. Sometimes NULL is displayed in a different color, but sometimes it is not.
Visual Assist makes it super easy to apply the fix, and it’s a very safe change.
3. Convert enum to scoped enum
Another pattern that is enhanced with Modern C++ is a way you can define enums.
It was popular to write the following code:
enum ActionType { atNone, atProcess, atRemove, atAdd }; ActionType action = atNone; Since C++11 it's better to you can define this type in the following way: enum class ActionType { None, Process, Remove, Add }; ActionType action = ActionType::None;
What are the benefits of such transformations?
- They don’t pollute the global namespace. As you may have noticed, it was often necessary to add various prefixes so the names wouldn’t clash. That’s why you see atNone. But in the scoped enum version, we can write None.
- You get strong typing, and the compiler will warn when you want to convert into some integral value accidentally.
- You can forward scope enums and thus save some file dependencies.
What’s best about Visual Assist is that it has a separate tool to convert unscoped enums to enum classes, all with proper renames and changes in all places where this particular type was used. Right-click on the type and select “Convert Unscoped Enum to Scoped Enum.”. This opens a preview window where you can see and select which references will be replaced.
Read more in Convert Unscoped Enum to Scoped Enum in the VA documentation.
4. Use more auto
One of the key characteristics of Modern C++ is shorter and more expressive code. You saw one example where we converted from for loops with long names for iterators into a nice and compact range-based for loops.
What’s more, we can also apply shorter syntax to regular variables thanks to automatic type deduction. In C++11, we have a “reused” keyword auto for that.
Have a look:
std::vector<int> vec { 1, 2, 3, 4, 5, 6, 7, 8}; std::vector<int>::const_iterator cit = vec.cbegin();
We can now replace it with:
std::vector<int> vec { 1, 2, 3, 4, 5, 6, 7, 8}; auto cit = vec.cbegin();
Previously, template-type deduction worked only for functions, but now it’s enabled for variables. It’s similar to the following code:
template <typename T> func(T cit) { // use cit... } std::vector<int> vec { 1, 2, 3, 4, 5, 6, 7, 8}; func(vec.cbegin()); // template deduction here!
Following are some other examples:
auto counter = 0; // deduced int auto factor = 42.5; // deduces double auto start = myVector.begin(); // deduces iterator type auto& ref = counter; // reference to int auto ptr = &factor; // a pointer to double auto myPtr = std::make_unique<int>(42); auto lam = [](int x) { return x*x; };
Have a look at the last line above. Without auto, it wouldn’t be possible to name a type of a lambda expression as it’s only known to the compiler and generated uniquely.
What do you get by using auto?
- Much shorter code, especially for types with long names
- Auto variables must always be initialized, so you cannot write unsafe code in that regard
- Helps with refactoring when you want to change types
- Helps with avoiding unwanted conversions
As for other features, you’re also covered by Visual Assist, which can automatically apply auto. In many places, you’ll see the following suggestions:
This often happens in places like
SomeLongClassName* pDowncasted = static_cast<SomeLongClassName*>(pBtrToBase); // no need to write SomeLongClassName twice: auto pDonwcasted = static_cast<SomeLongClassName*>(pBtrToBase);
or
unsigned int val = static_cast<unsigned int>(some_computation / factor); // just use: auto val = static_cast<unsigned int>(some_computation / factor);
As you can see, thanks to auto, we get shorter code and, in most cases, more comfortable to read. If you think that auto hides a type name for you, you can hover on it, and the Visual Studio will show you that name. Additionally, things like “go to definition” from Visual Assist work regularly.
5. Deprecated functionality
With C++11, a lot of functionality was marked as deprecated and was removed in C++17. Visual Assist helps with conversion to new types and functions.
For example, it’s now considered unsafe to use random_shuffle as it internally relied on simple rand(), which is very limited as a random function generator.
std::vector<int> vec { 1, 2, 3, 4, 5, 6, 7, 8 }; std::random_shuffle(vec.begin(), vec.end());
Visual Assist can replace the above code with the following:
std::shuffle(vec.begin(), vec.end(), std::mt19937(std::random_device()()));
Additionally, you’ll get suggestions to improve old C-style headers and convert them into C++ style:
Some other handy fixes
We have covered a few inspections, but there’s much more! Here are some exciting items that you might want to see:
- Standard algorithm can be replaced by container’s more efficient implementation: clang-tidy – performance-inefficient-algorithm
- typedef can be converted to using declaration: clang-tidy – modernize-use-using
- Redundant call to c_str() or data() on string: clang-tidy – readability-redundant-smartptr-get
- Optimize call to std::string::find() to use single character literal: clang-tidy – performance-faster-string-find
The whole list is available here: List of Code Inspections
Summary
Thanks for reading our small series on Clang Tidy, Code Inspections, and Visual Assist. We covered a lot of items, and I hope you learned something new. The techniques I presented in most cases are very easy to use, especially thanks to VA support, and you can gradually refactor your code into Modern C++.