How to Modernize C++ Code with Visual Assist in Five Easy Steps

How to Modernize C++ Code with Visual Assist in Five Easy Steps

You probably know that over time our projects seem to get old and legacy. Code written now might look suspicious five years later. With the standardization of C++11 in 2011, developers coined the term Modern C++. In this article (and the next one) we’ll take a look at some techniques you might apply to get nicer code and be closer to the best feature that recent versions of C++ offer. 

Let’s start! 

1. Rename and Aim for Meaningful Names You might be surprised by the first point on our list. 

Is rename added to C++11 as a feature? 

No, it’s definitely not just an element of C++. In fact, having good names is not a feature of any programming language, as it depends on your style. The compiler can take any names, including single letters and even almost any Unicode character. Thanks to better IDE support and powerful text editors, we can now avoid shortcuts and rely on full and meaningful names. 

And what’s most important is that we can leverage extra refactoring tools, like those from Visual Assist, to fix code with the legacy style. 

Let’s have a look at the following class: 

class PBrush { 
public: 
	PBrush(); 
	~PBrush(); 

	bool Gen(HDC r) { } 
	bool IsAval() const { return aval; } 

private: 
	bool aval; 
	
	HGLOBAL m_hGlobal; 
	LPBITMAPINFO lpBitmap; 
	LPBYTE lpBits; 
	HDC ReferenceDC; 
	
	RGBQUAD m_pal[256]; 
}; 

Read the above code and think about potential naming issues. Is the code clear and descriptive? What would you change? 

Or take a look at the following function, which uses this PBrush objects: 

std::vector<PBrush*> FilterAvalBrushes(const std::vector<PBrush*>& brs) { std::vector<PBrush*> o;
	ULONG totalBCount = 0; 
	size_t totalBitmaps = 0; 
	
	for (size_t i = 0; i < brs.size(); ++i) { 
		if (brs[i]->IsAval()) { 
			o.push_back(brs[i]); 
			++totalBitmaps; 
			totalBCount += brs[i]->GetByteCount(); 
		} 
	} 
	
	// make sure they have some bits ready: 
	for (size_t i = 0; i < o.size(); ++i) { 
		if (!o[i]->Bits()) 
			Log("ERROR, empty bitmap!"); 
	} 
	
	Log("Byte count %d, total bitmaps %d", totalBCount, totalBitmaps); return o; 
}

How could we improve the code? 

You might be tempted to say that it’s better to leave old code and not touch it since other systems might depend on it. However, adding or changing to better names and keeping it useful to other systems is very simple. In most cases, you can find and replace old names or use Rename from Visual Assist. 

Visual Assist has a powerful feature for renaming objects and making sure the code compiles after this transformation. It was also one of the earliest VA features and enabled using this refactoring capability years before Visual Studio added it. 

You can invoke the rename tool in many situations. 

The simplest way is to hit a key shortcut when your cursor is inside a particular name ( Shift + Alt + R by default). For example, let’s say that I’d like to rename bool aval, so I move my cursor there and invoke the dialog, and then I can see the following window: 

Another option is to write a new name, and then VA will show a handy context menu where you can invoke the rename or see the preview window before making the changes. 

What other things can you do with rename? 

Improve consistency of names. For example, your style guide might suggest adding m_ before each nonstatic data member for classes. So you might quickly improve it and add this prefix for classes that don’t share this style. In our example, some names start with m_ while others don’t. 

It’s not only variables and functions but also things like enumerations and namespaces. 

But most importantly, what name would be better for aval? Do you prefer m_isAvailable ? or Available ? I’ll leave it as an exercise. 

2. Extract Smaller Functions 

Here’s another example to fix: 

// number of data components 
switch ( format ) 
{ 
case GL_RGB: 
case GL_BGR_EXT: components = 3; break; 
case GL_RGBA: 
case GL_BGRA_EXT: components = 4; break; 
case GL_LUMINANCE: components = 1; break; 
default: components = 1; 
} 

GLuint t; 
glGenTextures(1, &t); 

// load data 
HBITMAP hBmp = (HBITMAP)LoadImage(NULL, fname, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION); 
if (hBmp == NULL) 
	return 0; 
	
// rest of code... long code

The above code is just a part of some longer function that loads, opens a file, loads bytes, and creates an OpenGL texture object with the loaded data. One thing that we might immediately do is to make this function smaller. More compact functions are easier to read and allow for more code reuse.  We can see that it has several smaller subtasks, so it would be better to extract them into separate parts. For example, we can implement FormatToComponents

And when you select this context menu, you’ll get the following dialog: 

After some adjustments (for example, we don’t need to take components as input argument), we can prepare the following code: 

int FormatToComponents(GLenum format) { 
	switch (format) { 
	case GL_RGB: 
	case GL_BGR_EXT: return 3; 
	case GL_RGBA: 
	case GL_BGRA_EXT: return 4; 
	case GL_LUMINANCE: return 1; 
	} 
	return 1; 
} 

And then call it from our initial function: 

GLuint LoadTextureFromBmp(const char *fname, GLenum format, GLuint filter) { const int components 
	= FormatToComponents(format);  

This not only gave us shorter, cleaner code, but also we can now mark our variable as const, which even improves the quality. Additionally, this function might be reused in some other places of the system. 

Here are the general rules for the extraction by VA: 

Symbols available globally, or within the class of the original and new methods, are not passed via a parameter list. 

Symbols used only in the right side of expressions (i.e., not modified) are passed by value. Symbols used in the left side of expressions (i.e., assigned) are passed by reference. Symbols referenced with dot notation within the extracted code (e.g., classes and structs) are always passed by reference. 

When a newly created method assigns a value to only one symbol, that symbol is passed by value and the assignment is done in the original method via return value. 

Symbols local to extracted code become local to the new method; they are not passed.

 3. Improve Function Interfaces 

Let’s now challenge ourselves with this one: 

bool Init2d(HDC hdcContext, FontMode fm, HFONT hFont, GLuint iWidth, GLuint iHeigth, GLuint iTexture, bool forceBold, bool forceItalic, size_t numGlyphs); 

It’s a member function in GLFont that is responsible for creating a texture font from a Window font. We can later use this created object to draw text in an OpenGL application. 

Isn’t it worrying that this function takes nine input arguments? 

And here’s an even more suspicious-looking call site: 

glFont.Init2d(const_cast<CGLApp *>(glApp)->GetHdc(), fmBitmap, hFont, iWidth, iHeight, 0, false, false, 256);

Some books suggest that if you have more than 5… or 7, then it’s some worrying code smell.  Such long lists of function arguments are hard to read, and caller sites might easily put them in a wrong order. Another issue is that there are a few boolean parameters, and it’s hard to see what they mean from the caller perspective. 

You can use Change Signature to modify all parts of a signature, including: 

  • Method name 
  • Return type 
  • Visibility 
  • Parameter names 
  • Parameter types 
  • Parameter order 
  • Number of parameters 
  • Qualifiers 

The plan for our function is to introduce a structure that would hold all parameters. We can do it by simply copying the parameter list and putting it into a structure above the function declaration: 

struct FontCreationParams { 
	GLuint iWidth; 
	GLuint iHeigth; 
	GLuint iTexture { 0 }; 
	bool forceBold { false}; 
	bool forceItalic { false}; 
	size_t numGlyphs { 256 }; 
}; 

As you can see, I even assigned some default values. 

And then we can change the signature in two steps: 

1. Add new argument to the function: const FontCreationParams& param with a default value of TODO. 

2. And then, again remove those extra arguments. 

Then you might compile code and fix call sites. 

The final code might look as follows: 

FontCreationParams params; 
params.iWidth = iWidth; 
params.iHeigth = iHeight; 
glFont.Init2d(const_cast<CGLApp *>(glApp)->GetHdc(), fmBitmap, hFont, params);

With this approach, if you need some more parameters (or you need to alter them), it’s just a matter of modifying the structure, and the function declaration will be the same. 

While you can do such a change manually, Visual Assist makes it much more comfortable and takes care of most of the cases at the call site. So this significantly simplifies and automates the process when you have many function calls. 

So far, we discussed basic principles for code modernization, but how about automation? Can we use extra tools that would tell us what to change in our code? 

Let’s see the fourth point. 

4. Enable Code Inspections 

Since a few years ago, Visual Assist has come with code Inspections that automatically check your code and pinpoint any issues. 

Code Inspection analyzes C/C++ for specific code quality issues as you edit. The feature, based on LLVM/Clang, warns you of issues in your code and if possible, suggests and applies quick fixes. 

You can enable them via Settings or as shown below: 

You can read the full description of Inspections in the excellent documentation website Introduction to Code Inspection and also see our previous article where we dive a bit deeper A Brief Introduction to Clang-Tidy and Its Role in Visual Assist – Tomato Soup. 

Currently,  we have almost 30 code inspections, and the number grows with each revision: List of Code Inspections. 

They range from checking .empty() vs size() , using emplace_back(), nullptr usages, and many more. They can help not only with code modernization but even with finding some performance issues in code. It’s like a small code analysis run on the code. 

Let’s  take a look at one related to for loops. 

5. Make For-Loops More Compact 

One of the coolest elements of C++11 style comes from using simpler for loops. This is especially important for code that uses containers from the Standard Library. 

For example, before C++11, you had to write: 

std::vector { }... 
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) std::cout << *it << '\n'; 

Now, this can be simplified with this simple syntax: 

std::vector<int> vec { 1, 2, 3, 4, 5, 6 }; 
for (const auto& elem : vec) 
	std::cout << elem << '\n';

Why not automate this transformation? 

VA identifies places where you can use this modern syntax and then rewrites loops for you. 

Consider this example: 

std::vector<PBrush*> FilterAvalBrushes(const std::vector<PBrush*>& brushes) { // ... 
	for (size_t i = 0; i < brushes.size(); ++i) { 
		if (brushes[i]->IsAval()) { 
			out.push_back(brushes[i]); 
			++totalBitmaps; 
		} 
	} 
} 

When code inspections are enabled, we can see the following suggestions:

What’s nice about Visual Assist is that it’s very smart and tries to deduce the correct loop syntax. 

For example, when our vector holds pointers, then the converted loop is as follows: 

std::vector<PBrush*> FilterAvalBrushes(const std::vector<PBrush*>& brushes) { // ... 
	for (auto brushe : brushes) { 
		if (brushe->IsAval()) { 
			out.push_back(brushe); 
			++totalBitmaps; 
			totalBCount += brushe->GetByteCount(); 
		} 
	} 
} 

But when we change the type into const std::vector<PBrush> (so it holds not pointer but “full” objects), then the loop is as follows: 

for (const auto & brushe : brushes) { 
	if (brushe.IsAval()) { 
		out.push_back(brushe); 
		++totalBitmaps; 
		totalBCount += brushe.GetByteCount(); 
	} 
}

The system is smart and avoids situations where you’d copy objects during loop iteration, as it’s more efficient to use references. 

That’s just a taste of what you can automatically do with Visual Assist and its code inspections! 

Summary 

In this article, you’ve seen five suggestions on how to check and improve your code. We started with something fundamental but often skipped: naming. Then we made functions smaller and with better interfaces. And at the end, we enabled code inspections so that Visual Assist can automatically identify more things to repair. 

In the last item, we looked at loop transformation, which is available from C++11. Thanks to Visual Assist, we can automate this task and quickly modernize our code to the newer standard 

After such refactoring, we aim to have safer, more readable code. What’s more, with more expressive code, we can help the compiler to identify potential errors. This is just a start, and stay tuned for the next article where you’ll see other code modernization techniques: override keyword, nullptr, enum class, auto type deduction, and deprecated headers.

Getting Started with UE4 and Visual Assist

Whether you’re new to Visual Assist and UE4 or a seasoned vet, we thought you might appreciate a little more insight into what you can expect and how to get started. Thanks to our resident UE4 wizards for putting this together.

1. Install Visual Assist

  1. Exit all instances of Visual Studio.
  2. Run the .exe installer you downloaded.
  3. Select the IDE(s) you want to install to.

2. Open your game solution

Visual Assist will come alive after it finishes parsing.

3. Look around

Open the Extensions > VAssistX menu. You will use the menu primarily to open tool windows, review keyboard shortcuts, and access the options dialog.

Appreciate the understated UI to Visual Assist. There are just a few visible changes.

4. Change a few settings

If you like meaningful syntax coloring, open the options dialog for Visual Assist and apply coloring to more of the UI.

If you highlight the current line, choose a thin frame that doesn’t obscure your code.

Visual Assist can add important information to tooltips when hovering over a symbol, such as comments from base classes. This is very helpful in UE4, as base class comments are the documentation.

Visual Assist can analyze your code and suggest improvements. Enable Code Inspections to see blue underlines where code might be improved. Visual Assist can even modernize your code for you! We will show how later.

Then, open the options dialog to Visual Studio and eliminate the redundant navigation bar. The version in Visual Assist includes the functionality of the built-in one.

Disable built-in navigation bar

After making the changes, Visual Studio is ready to use.

5. Navigation in UE4

Search for and open files using Alt + Shift + O. Precede a search filter with a hyphen to exclude symbols (negative filtering).

Understand and navigate the inheritance hierarchies of UE4 by using Alt + Shift + G on a class name. The Alt + Shift + G shortcut works on many types of symbols, try using it on a virtual method.

Source files in UE4 can be thousands of lines long. Use Alt + M to search for and navigate to methods inside the current file.

Find references to a symbol using Alt + Shift + F. Visual Assist’s find references is fast and accurate inside huge solutions like UE4. Try cloning your results to save them by clicking the Clone Results button or using the right-click menu.

Hover over a virtual method to see comments from base classes. Base class comments often contain useful documentation.

6. Refactoring in UE4

Access refactoring tools using the keyboard shortcut Alt + Shift + Q, or by hovering over a symbol and clicking the tomato icon that appears. The contents of the Alt + Shift + Q menu depend on the symbol.

It is common in UE4 to override virtual methods, such as Tick or SetupPlayerInputComponent. Visual Assist can implement these methods for you. Click on your class name and then use Alt + Shift + Q.

The Implement Methods dialog is searchable, and you can implement more than one method at once.

Visual Assist will intelligently add a call to the Super class version of the method for you when appropriate.

You will see blue underlines below code which could be modernized. This is Code Inspection.

Visual Assist can refactor the code for you! Use Alt + Shift + Q on the underlined symbol.

If you need to change the return type, parameters, or the name of a method you can use Change Signature. Edit the method definition in the window. References and call-sites to the method will be updated, so you won’t miss anything.

7. Tips

There is a lot of special functionality built into Visual Assist for UE4, such as suggestions for U* macros. The more you can use Visual Assist, the more opportunities to make your life a little easier in UE4.

You may find the built in IntelliSense to be unusably slow, or that it often adds red underlines to correct code in UE4. IntelliSense can be disabled. Visual Assist provides all the intelligent tooling expected in a modern C++ development environment.

You can throttle the initial parse in the Performance tab of the Visual Assist options dialog. By default, the one-time parse uses all available resources to finish as fast as possible.

For more information or support check out our forum and documentation.

Some features mentioned above require the latest build, check here for updates.