Best Practices for C++ Development

c++development

1. Use the Tools Available

In the early phase of the development process, an automated framework needs to define for source code management, build, execute and test. It should not take more than 2-3 commands to check out the source code, build, and execute the tests. Various tools are available for this purpose.

1.1.Source Control Tools:

  • GitHub – allows for unlimited public repositories, must pay for a private repository.
  • Bitbucket – allows for unlimited private repositories with up to 5 collaborators, for free.
  • SourceForge – open source hosting only.
  • GitLab – allows for unlimited public and private repositories, unlimited CI Runners
    included, for free.
  • Visual Studio Online – allows for unlimited public repositories, must pay for private repository

1.2.Build Tool

    Use an industry standard widely accepted build tool. This prevents you from reinventing the wheel whenever you discover / link to a new library / package your product / etc. Some examples are CMake, Conan, FASTBuild, CPPAN, Ninja, etc.

1.3.Continuous Integration

    Continuous Integration (CI) tools automatically build the source code as changes are pushed to the repository.

  • Travis CI: works well with C++. Designed for use with GitHub. Free for public repositories on GitHub.
  • AppVeyor: Supports Windows, MSVC and MinGW, Free for public repositories on GitHub.
  • Hudson CI / Jenkins CI: Java Application Server is required supports Windows, OS X, and Linux extendable with a lot of plugins.
  • Visual Studio Online: Tightly integrated with the source repositories from Visual Studio Online

1.4.Compilers

    Use every available and reasonable set of warning options. Some warning options only work with optimizations enabled, or work better the higher the chosen level of optimization is, for example -Wnull-dereference with GCC.
    You should use as many compilers as you can for your platform(s). Each compiler implements the standard slightly differently and supporting multiple will help ensure the most portable, most reliable code.

1.5.Static Analyzers

    Use the static analyzer to run as part of your automated build system. Cppcheck and clang tools could meet the requirement as free tools.

1.6.Runtime Checkers

  • Memory usage validators: Runtime code analyzer that can detect memory leaks, race conditions, and other associated problems. Some of the good tools are Valgrind, Dr Memory, etc.
  • Code Coverage Analysis : A coverage analysis tool shall be run when tests are executed to make sure the entire application is being tested. Some of the good tools are Codecov, Coveralls, LCOV etc.

1.7.Testing Tools

    Make sure whatever build system you use has a way to execute tests built in. To further aid in executing tests, consider a library such as Google Test, Catch, CppUTest or Boost.Test to help you organize the tests.


2. Style

Consistency is the most important aspect of style. The most important aspect is following a style that the average C++ programmer is used to reading. So, establish a style guideline. Define the guideline for the areas like Common Naming Conventions, Distinguish Private Object Data, Distinguish Function Parameters, Comments formats, Block definition formats, etc.


3. Considering Safety

  • Const as Much as Possible : const tells the compiler that a variable or method is immutable. This helps the compiler optimize the code and helps the developer know if a function has a side effect. Also, using const & prevents the compiler from copying data unnecessarily.
  • Carefully Consider Your Return Types : Returning by & or const & can have significant performance savings when the normal use of the returned value is for observation Returning by value is better for thread safety and if the normal use of the returned value is to make a copy anyhow, there’s no performance lost If your API uses covariant return types, you must return by & or *. For temporaries and local variables Always return by value.
  • Do not pass and return simple types by const ref: Because passing and returning by reference leads to pointer operations instead of much more faster-passing values in processor registers.
  • Avoid Raw Memory Access.

  • Use std::array or std::vector Instead of C-style Arrays.
  • Use Exceptions: Exceptions cannot be ignored. Return values, such as using boost:: optional, can be ignored and if not checked can cause crashes or memory errors. An exception, on the other hand, can be caught and handled.
  • Do not define a variadic function : Variadic functions can accept a variable number of parameters. The probably best-known example is printf(). You have the possibility to define this kind of functions by yourself but this is a possible security risk. The usage of variadic functions is not type safe and the wrong input parameters can cause a program termination with an undefined behavior.

  • 4. Considering Maintainability

  • Avoid Compiler Macros: Compiler definitions and macros are replaced by the preprocessor before the compiler is ever run. This can make debugging very difficult because the debugger doesn’t know where the source came from.
  • Consider Avoiding Boolean Parameters: They do not provide any additional meaning while reading the code. You can either create a separate function that has a more meaningful name, or pass an enumeration that makes the meaning more clear
  • Avoid Raw Loops.

  • Properly Utilize ‘override’ and ‘final’ : These keywords make it clear to other developers how virtual functions are being utilized, can catch potential errors if the signature of a virtual function change, and can possibly hint to the compiler of optimizations that can be performed.

  • 5. Considering Portability

    Know Your Types : Most portability issues that generate warnings are because we are not careful about our types. Standard library and arrays are indexed with size_t . Standard container sizes are reported in size_t . If you get the handling of size_t wrong, you can create subtle lurking 64-bit issues that arise only after you start to overflow the indexing of 32-bit integers. char vs unsigned char.


    6. Considering Threadability

  • Avoid Global Data : Global data leads to unintended side effects between functions and can make code difficult or impossible to parallelize. Even if the code is not intended today for parallelization, there is no reason to make it impossible for the future.
  • Statics: Besides being global data, statics are not always constructed and deconstructed as you would expect. This is particularly true in cross-platform environments.
  • Shared Pointers : std::shared_ptr is “as good as a global” (http://stackoverflow.com/a/18803611/29975) because it allows multiple pieces of code to interact with the same data.
  • Singletons : A singleton is often implemented with a static and/or shared_ptr.
  • Avoid Heap Operations : Much slower in threaded environments. In many or maybe even most cases, copying data is faster. Plus with move operations and such and things.

  • 7. Considering Performance

    7.1.Build Time

    • Forward Declare When Possible.
    • Avoid Unnecessary Template Instantiation : Templates are not free to instantiate. Instantiating many templates, or templates with more code than necessary increases compiled code size and build time.
    • Avoid Recursive Template Instantiations.
    • Don’t Unnecessarily Include Headers : The compiler has to do something with each include directive it sees. Even if it stops as soon as it seems the #ifndef include guard, it still had to open the file and begin processing it.
    • Consider using precompiled headers.

    7.2.Runtime

    • Analyze the Code to find out real bottlenecks.
    • Simplify the Code.
    • Use Initializer Lists: Initializer lists are significantly more efficient; reducing object copies and resizing of containers.
    • Reduce Temporary Objects.
    • Enable move operations : Move operations are one of the most touted features of C++11. They allow the compiler to avoid extra copies by moving temporary objects instead of copying them in certain cases
    • Kill shared_ptr Copies : shared_ptr objects are much more expensive to copy than you’d think they would be. This is because the reference count must be atomic and thread-safe.
    • Reduce Copies and Reassignments as Much as Possible.
    • Avoid Excess Exceptions
    • Get rid of “new” : We already know that we should not be using raw memory access, so we are using unique_ptr and shared_ptr instead.
    • Get rid of std::endl : std::endl implies a flush operation. It’s equivalent to “\n” << std::flush.
    • Limit Variable Scope : Variables should be declared as late as possible, and ideally only when it’s possible to initialize the object. Reduced variable scope results in less memory being used, more efficient code in general, and helps the compiler optimize the code further.
    • Prefer double to float , But Test First : Depending on the situation and the compiler’s ability to optimize, one may be faster over the other. Choosing float will result in lower precision and may be slower due to conversions. On vectorizable operations float may be faster if you are able to sacrifice precision.
      Prefer ++i to i++ : Pre-increment is faster than post-increment because it does not require a copy of the object to be made.


    Conclusion

    Whenever there is a need for performance-driven enterprise class product development, C++ is widely recognized as a choice by default. Variety of applications use C++ programming, and at OdiTek we have a pool of vastly experienced CPP developers. We will be happy to assist you to plan & do product development, drop us an email on info@oditeksolutions.com

    Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+

    Leave a Reply

    Your email address will not be published.Required fields are marked *