If you’re working on a C++ application, troubleshooting some bugs, or struggling to make C and C++ code work together, we’re here to help. In this article, we’re taking a look into the inner workings of C++. There are some simple C++ best practices and principles, like understanding compilation and linking processes, that can make your code cleaner, save you time and make working on your project a whole lot more enjoyable. We cover:
- C++ Best Practices for Getting Started with Compilers
- Guide to C++ Compilation and Linking Processes
- How to Export C Functions
- When to Use C++ Header Guards
- Top 10 Most Common C++ Mistakes That Developers Make
Keep reading to learn more about how C++ works and how you can apply these C++ best practices to make you a stronger developer.
C++ Best Practices for Getting Started with Compilers
If you’re just getting started with C++, the first step will be working with a compiler. The purpose of C++ compilers is to read programs and convert it into object code. Compilers are simply programs that work through command-line interfaces, also known as CLIs. When compilers convert your program’s code into object code, it’s merely translating it so the computer can execute it directly.
You can download one of the many compilers available for Windows, Linux, or Mac to get started. Or, you can simply get started with C projects in-browser.
Toptal.com provides a great list of compilers in the article How to Learn the C and C++ Languages: The Ultimate List.
Guide to C++ Compilation and Linking Processes
Understanding the purpose of C++ compilation and linking is simple, yet, surprisingly few developers have a comprehensive understanding of the process. Each C++ source file must be compiled into an object file. Once compiled into an object file, numerous source files are then able to be linked into a shared or static library or an executable file.
There are three steps to the compilation of a C++ program:
- Preprocessing
- Compilation
- Linking
First, the compiler will run the preprocessor over the source code files. Keep in mind header files aren’t sent to the compiler, they’re already included in the source files. These header files can be opened many times while the preprocessor is running over the source files. The source files, however, will only be opened once. While the preprocessor is running, it works on the preprocessor directives, such as #include. This means for the directive #include; the preprocessor will replace the line with #include with the contents of the included file. The preprocessor will also remove some code from the headers and source code files when it finds conditional compilation blocks that evaluate to false. Once complete, the preprocessor outputs a C++ file that doesn’t contain any preprocessor directives.
Second, the compiler takes the output from the preprocessor and makes an object file. In order for the compiler to obtain the translation unit from the preprocessor, the -E option can be used to pass it to the g++ compiler. Then, the -o option can be used to assign a name to the preprocessed source file.
To do this, simply open the cpp-article/hello-world directory. You should see the “hello-world.cpp” file as an example.
You can create the preprocessed file by entering:
g++ -E hello-world.cpp -o hello-world.ii
You can also see the number of lines by entering:
wc -1 hello-world.ii
Alternatively, you can run the command make on the directory above, and it’ll automatically process through those steps. Most files will have a large number of lines compared to your simple source file. This is normal since the compiler created a file that included all headers. The more headers in your source code file, the bigger the translation unit becomes.
Lastly, the linker takes the object files from the compiler and produces a static or shared library or an executable file.
How to Export C Functions
While you can export C functions for use in C, you can also export them to use in C++ language executables. In order to do this, you’ll want to use the _cplusplus preprocessor macro. That’ll automatically determine which language is being compiled, and then declare and link it.
If you do this and provide header files for your DLL, there won’t be any change when using these functions with C or C++.
Here’s an example of a header file that can be used by both C and C++ clients:
When to Use C++ Header Guards
If you’ve been coding with C++ for any amount of time, you likely know not to include your headers twice from the same source file. However, the same header can be included more than once indirectly if one of your headers includes other headers. It’s incredibly easy to end up with multiple declarations since header content is put in from the place where it was included.
Top 10 Most Common C++ Mistakes That Developers Make
C++ developers of all skill levels and years of experience have some common pitfalls to be wary of. The more you can follow C++ best practices, the easier (and less expensive) your code will be to maintain. There’s a lot to learn in C++. Simply understanding the language syntax, or thinking that you have enough transferable knowledge from similar programming languages like JavaScript or C isn’t enough to write good, clean code in C++. We recommend avoiding these top 10 most common C++ mistakes that developers make.
#1 Not Following Consistent Naming Conventions
Let’s be honest. We’ve all been there. Whether it’s frustration at your past self, or when jumping onto someone else’s project, using inconsistent naming conventions can cause a lot of unnecessary work decoding what your code actually means. Keep your code clean by establishing proper naming conventions right at the start of your project. A Few C++ best practices for naming include:
- Use CamelCase for Types (MyClass)
- Functions and variables should start with lowercase (myMethod)
- Keep constants in all caps (const int PI=3.141592)
Keeping your naming conventions clear at the beginning of your project will save you an incredible amount of time when you go back to look at your code. For instance, if you see a name in CamelCase, you’ll know it’s the name of a class. Or perhaps if you see a name in all caps, you’ll know it’s a constant.
#2 Passing an Object By Value
Passing objects by value will impact your program’s performance. Many C++ developers take this shortcut to save on typing a few extra characters, or with the intent of returning later to fix it. And, if we’re honest with ourselves, the likelihood of actually going back to fix it is pretty slim.
When you pass an object by value, it can cause your code to perform in unexpected ways. Simply passing objects by reference, not by value, may seem like more work initially, but trust us, you’ll be thankful in the long run. It’ll save you time and headaches.
#3 Using Name Hiding Incorrectly
Many developers wonder when to use C++ name hiding. It can be useful in some instances, but can also cause some challenging bugs, especially if you’re using name hiding for declarations. However, these mistakes can be avoided by using the right coding rules. For instance, deciding that identifiers of an inner scope won’t hide an outer scope identifier can help avoid bugs in your code.
#4 Type Conversions
C++ offers developers a lot of flexibility; this can be both a strength and a weakness. If misunderstood, it can lead to value-sensitive bugs when using type conversions. Take, for instance, the coding example below:
The resulting value from this code could be 70000 or 4464, depending on how the typedefs are defined. Avoid this common mistake by implementing, once again, the right coding rules. A simple rule stating, ‘a composite expression’s value won’t be assigned to an object that is with a wider essential type,’ will help you avoid this.
#5 Referencing a Deleted Resource
If you’re using a multithreaded application, you’ve likely encountered this issue more times than you care to admit. If you have more than one thread that uses the same connection ID, your program will end up with undefined behavior.
To avoid this, don’t use pointers or references to resources in the event another thread deletes it. Instead, use smart pointers with reference counting.
#6 Misusing User-Defined Conversions
User-defined conversions can be incredibly useful; however, when used incorrectly, they can lead to unpredicted conversions that can be incredibly difficult to find.
To avoid this, we recommend assigning an explicit keyword to deny implicit conversions. Additionally, make sure to use explicit conversation methods instead of conversion operators.
#7 Logic Errors Causing Unreachable Code
Flaws in coding logic can create a wide array of issues, like creating code that can’t be executed. Algorithms may or may not be able to find if there is unreachable code in your project. However, using a static analysis tool can help you track down and fix any flaws in logic, causing unreachable code.
#8 Not Maintaining a Program Log or Commenting Your Code
Ensuring that your production system records what it’s doing can give you insight into what requests worked, and what didn’t (and why). This can help you re-create errors to work through and ultimately fix them.
This goes along with the common error of not commenting on your code. Comments can be included in your code to give more clarity and insight to the user reading your code. In C++, any line starting with ‘//’ is considered a comment and is completely ignored by the compiler. Comments should briefly give users more insight into the program’s functionality.
For example, consider this code:
Now consider the following code, with comments:
The latter option provides more clarity to the user about the functionality of the code.
#9 Not Using a Debugger
No one likes bugs, especially programmers. This might be more of a beginner’s mistake, but it’s worth visiting. Using a debugger can help you save time in finding bugs or potential bugs and working through them.
#10 Not Backing Up Your Work
No matter how seasoned a developer you are, backing up your work is extremely important. Losing any amount of work is entirely avoidable by implementing weekly or even nightly backups. Setting up an automated system to backup your work is relatively inexpensive when compared to the risk of losing your work.
Conclusion
Whether you’re troubleshooting bugs in your program, trying to get C and C++ to work together, or working on an app in C++, we hope this article helped! Understanding the inner workings of C++ can help you save time, headaches, and make your coding process easier. Understanding how C++ works with compilation and linking processes, how to export C functions, or when to use C++ header guards can help you write better code that is easier for you or anyone jumping on your project to understand. If you apply these C++ best practices to your projects, you’ll create cleaner, better code.
If you’re looking for more ways to save time while coding, we recommend picking up a free download of Zight (formerly CloudApp). This simple developer tool is used by 53% of Fortune 500 companies. It allows you to screen record, capture screen snippets, annotate screenshots and even GIFs in a few seconds.