C++ – Best Practices for Defining Member Functions Directly in a Class

cclassdefinitionfunctions

I'm a beginner in C++, and I was wondering if it is best practice to define a member function directly in a class, such as:

// something.hpp
class C {
    inline int func() { return ... ; }
}

rather than the usual:

// something.hpp
class C {
    int func();
}
// something.cpp
int C::func() { return ... ; }

In my reading of Stroustrup's "The C++ Programming Language", I have seen him use either technique. Alas, I am confused as to whether it is good to do so in real-world applications and, in particular, the creation of libraries.

Best Answer

When a function is defined as part of the class definition, it is implicitly inline (regardless of whether you use that keyword). This has a number of consequences.

  • The function may be inlined by the compiler, potentially but not necessarily resulting in a faster program.
  • Thus, different compilation units can get different copies of this function.
  • The One Definition Rule (ODR) does not apply, which usually requires that each function or object has only one definition in the entire program. Instead, you promise that all copies of the function are equivalent. (This could be violated if you change the function in the header, but don't recompile all compilation units that include the header).
  • Changing an inline function breaks ABI compatibility, requiring recompilation.

Whether the function is defined as part of the class definition, or as an inline function outside of the class definition but still within the header is equivalent:

class C {
    int func();
}

inline int C::func() { return ... ; }

Now when we put the function definition into a separate compilation unit, we have a different set of consequences:

  • The function cannot be inlined (unless your compiler does link-time optimization) which might be slightly less efficient.
  • The function is only defined in that compilation unit. To call the function from other compilation units, the object code has to be linked by the compiler.

The large practical difference is what happens when you modify this function.

  • For an inline function, you have to recompile all code that uses this definition.
  • For a non-inline function, you only have to compile that one compilation unit that defines it, and then re-link with other compilation units.

On large projects, avoiding recompilation is very important in order to enable fast feedback when making changes. This leads to various strategies:

  • keep header files as minimal as possible, possibly have multiple header files so that dependent compilation units can only pull in those declarations they need
  • prefer defining functions in separate compilation units
  • if compile time performance and ABI stability are more important than run time performance, the pImpl idiom can be used

However, inline functions still have their uses:

  • the function definition is trivial, unlikely to change, and should be inlined, e.g. getters or setters
  • templates are inline
  • the function should be available for inlining due to performance reasons

If you want to enable inlining optimizations within a compilation unit, declaring it as inline is not necessary or appropriate. It is more important that the function has “internal linkage”. For example, free functions (that are not class members) have internal linkage when declared static. The contents of an anonymous namespace have internal linkage. This is useful for helpers within a .cpp file.

If performance and compilation times and ABI stability are no concern, then defining your functions as part of the class definition is perfectly fine. This is the case in many smaller projects that are internally used libraries or executables. Some people prefer having the function definitions right in the class. Other people prefer keeping the definitions separate, so that it's easier to get an overview of all members of the class.

This answer applies to all versions of the C++ standard, with the caveat that the C++ 20 module system changes this. If a function is defined within a class definition that is part of a module, it will not be considered inline.

Related Topic