dcsimg

Understanding the Intricacies of Inline Functions in C++

WEBINAR:
On-Demand

Desktop-as-a-Service Designed for Any Cloud ? Nutanix Frame


Macros are used for efficiency in C. The C++ language inherited them as a legacy. There are issues with the use of preprocessor macros, especially when used in C++. Therefore, C++ provides a better alternative, called inline functions. Because inline functions are a derivative of macros, this article first gets into the key issues of macros and then dives into the intricacies of inline functions.

Preprocessor Directives

In C/C++, preprocessor directives are program statements preceded by the hash (#) symbol. Unlike other statements, these lines are a set of directives to the preprocessor, to resolve before actual compilation of the code can begin. A preprocessor directive typically begins and ends in a single line of code and there is no semicolon (;) to signify an end statement. A newline in a preprocessor directive usually means that the directive has ended. However, if required, we can extend a preprocessor directive over a single line by preceding a newline character at the end of the line by a backslash. The most common preprocessor directive we find in C/C++ is the #include statement. There can be many others, such as using #ifdef, #undef, #endif, and so forth.

Macros

A Macro is the next most commonly type preprocessor directives we find in a C/C++ program. Macros owe their origin to assembly language programming, where it is stated as a single computer instruction/statements which results in a series of instructions/statements. In C/C++, it begins with the #define statement and typically is used to name literal constants, providing a shorthand for a few well-chosen constructs. It is a convention to use capital letters for macro names only to make a distinction that it is not a typical function call. C++ took this a step further and introduced a design that makes the preprocessor almost unnecessary.

A Quick Example of How Preprocessor Directives Are Written

// Preprocessor directives
#include<stdio.h>
#include<math.h>

// Preprocessor directives - macro
#define MAX(X,Y) (X>Y)?X:Y
#define CIRCLE_AREA(R) \ M_PI*R*R

int main(void)
{
   #ifdef MAX
      printf("\nMAX is %d",MAX(10,20));   // This will be printed
   #endif
   #ifdef MIN
      printf("\nMAX is %d",MIN(10,20));   // MIN macro not defined
   #endif

   // A simple macro cal
   printf("\nCircle Area is %lf",CIRCLE_AREA(5.5));

   return 0;
}

Macros in C++

Macros appear to behave like functions without the overhead of invoking a normal function. C uses this to leverage efficiency. Because it uses the preprocessor rather than the compiler and replaces all macro calls with the macro definition, there is no cost involved as such in pushing the arguments, making an assembly language CALL, returning arguments and performing an assembly language RETURN. All the works are handled by the preprocessor. This provides utmost convenience and readability of a function call yet without any overhead cost involved.

It is perfectly all right to use macros in C++. There are a couple of issues involved as we dive deeper: First, although a macro behaves like a function call it is not one and the same thing. This can result in a situation where it is difficult to find bugs in the code. Secondly, in C++ a preprocessor cannot access class member data. Therefore, there is no way to use it as something like a class macro. The first problem is an issue for concern for C as well while the second one is specific to C++.

Having said that, we must consider that C++, by principle, is an object-oriented language where a class is an abstract data type declared to have its own properties and functions. Objects are created on the basis of the defined abstract data types. The properties and the functions have their boundaries or scope stated by the class definition. This idea collides with the idea of using the macro as a class member because it has no concept of scoping. A preprocessor simply performs text substitution wherever it finds the macro in the program. Every macro call is expanded in the place of its call and it is done by the preprocessor prior to beginning the compilation process. This idea is in contrast to any function call in C/C++. In a normal function call, the point of invocation only keeps the pointer to the location of actual function definition and never substitutes it with the definition. Macros are static in nature and do not allow any sort dynamic aspect to be imbibed within its definition.

Inline Functions in C++

C++ dealt with the issues of the macro by bringing it under the control of the compiler. The so-called inline functions are nothing but macros under the purview of C++ compiler. Unlike macros, the inline functions are actually functions in the fullest sense. The only similarity with the macro is that it gets expanded in place like the preprocessor macro and completely eliminates the overhead of s function. In short, we reap the benefits of a preprocessor macro minus its limitations. We can completely replace the use of a macro with inline functions in C++.

Inline Definition

Any function defined within the body of a class automatically becomes an inline function. However, if we want a non-class function to be inline, we can do it by using the keyword inline. We must define an inline function as soon as we declare it; otherwise, it will be treated as a normal function declaration. This means that

inline double circleArea(double rad);

will be treated as a normal function because it has no body although we have declared it with the inline keyword. To make it really inline for the compiler, we must define it in place of its declaration.

Inline double circleArea(double rad) { return M_PI*rad*rad; }

The interesting aspect with inline functions is that the compiler does the syntactic and semantic validation of the function argument and returns a value by performing conversion, if required, much like a normal function. This aspect is completely absent in the case of a preprocessor macro.

An Example to Illustrate Inline Functions

#include <iostream>
#include <cmath>

using namespace std;

class Circle {
   private:
      double radius;
   public:
      Circle(): radius(0){}
      Circle(double r): radius(r){}
      double getRadius() { return radius;}
      void calcArea();
};

inline void Circle::calcArea() {
   cout<<"\nArea of circle with radius="
      <<radius<<" is "<<(M_PI*radius*radius)
         <<endl;
}

int main(){
   Circle c1(10);
   c1.calcArea();
   return 0;
}

As we can see, there is not an iota of syntactical difference between an inline function and a normal function except for the use of the inline keyword, especially when we want to separate a declaration from the actual function definition (for example, calcArea). The difference, however, lies in the performance. This can almost tempt us to use inline everywhere even if we do not need them. We must keep in mind that the idea of inline is to provide optimization opportunities for the compiler to improve performance. This is ideal for small and scantily used functions. Using inline for a big or heavily used function will make a lot of duplicate code everywhere the function is called. This way, the code will not only bloat and but also be the very cause of low optimization and degraded performance. The elixir of immortality becomes the deadly poison. This is something definitely we do not want.

Conclusion

This obviously is not a comprehensive study of inline functions. But, perhaps this bit of comparative study between macros and inline functions provides some interesting insights into C++ intricacy. There is more to it as we dive deeper. We have a companion article coming up diving more into this topic. So, stay tuned. Happy Reading!



About the Author

Manoj Debnath

manosolireap@outlook.com

Related Articles

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date