Site icon Tomato Soup

How To Modernize With Visual Assist Part 2

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!

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?

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?

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:

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++.

https://www.wholetomato.com/downloads

 

Exit mobile version