3

I am developing a fairly large C++ support library, and have found myself moving towards a header-only approach. In C++ this almost works because you can implement where you define in classes. For templated methods, the implementation has to be in the same file anyway, so I find that it is much easier to just keep the implementation with the definition.

However, there are several times where "sources" must be used. As just one example, circular dependencies sometimes occur and the implementation has to be written outside the class definition. Here is how I am handling it:

//part of libfoo.h
class Bar
{
  void CircularDependency(void);
};

#ifdef LIBFOO_COMPILE_INLINE
void Bar::CircularDependency(void)
{
  //...
}
#endif

Then the project that uses libfoo would do the following in main.cpp:

//main.cpp
#define LIBFOO_COMPILE_INLINE
#include "libfoo.h"

And in any other .cpp:

//other.cpp
#include "libfoo.h"

The point is that the compile-inline section only gets compiled once (in main.cpp).

And finally my question: is there a name for this idiom or any other projects that work this way? It just seems to be a natural outcome of the implementation and definition being blurred by templating and class methods. And: are there any reasons why this is a bad idea or why it would potentially not scale well?

Quick aside: I know that many coders, with good reason, prefer their headers to resemble interfaces and not implementations, but IMHO documentation generators are better suited to describing interfaces because I like to hide private members all together :-)

Paul Roub
  • 36,322
  • 27
  • 84
  • 93
user4938472
  • 145
  • 8
  • 9
    Why not just put the definition in a Cpp file like anyone else instead of doing some error prone trick? – Mooing Duck Mar 23 '12 at 18:26
  • Does' not seem very natural to me. If you want to write Java then use Java. Importing their style to C++ seems like you just want to keep doing it the way you learned your original language. There are many articles on SO about learning to write code in the style appropriate to the language.http://programmers.stackexchange.com/q/112162/12917 – Martin York Mar 23 '12 at 18:31
  • 1
    @LokiAstari - actually this kind of trick (trying to get the implementation into the header) goes back way before Java (or heck, even C++!) was invented. I've seen code declaring it's variables in it's headers using a similar trick, but with a macro thrown on top for good measure. – Michael Kohne Mar 23 '12 at 18:40
  • 1
    If the need to provide template implementations in headers leads to this, this probably leads to just a single header file. Have you considered it? [At least with a single header file you can take control of the order of definitions and avoid the burden of defining macros on the people that include your library] – David Rodríguez - dribeas Mar 23 '12 at 19:15
  • @DavidRodríguez-dribeas That's actually what I'm doing ;-) I condense the whole library into a single header. (For better or for worse.) – user4938472 Mar 24 '12 at 21:42
  • 1
    If you did not understand the tags, let me tell you, rethink your design, not every library is meant to be header only, if you need to provide a compiled binary (or sources for the user to compile) Not every library in the standard is header only, not every library in boost is header only, and don't think you can outsmart the people that write the language or the people in boost. – David Rodríguez - dribeas Mar 25 '12 at 03:15

3 Answers3

4

You should be able to use the inline keyword if the circular dependency problem is resolved by the time the definition of Bar::CircularDependency() would show up in the libfoo.h header:

//part of libfoo.h
class Bar
{
  void CircularDependency(void);
};


// other stuff that 'resolves' the circular dependency
// ...
// ...

inline void Bar::CircularDependency(void)
{
  //...
}

That would make your library easier to use (the user wouldn't need to deal with the fact that LIBFOO_COMPILE_INLINE needed to be defined in exactly one place where the header is included).

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • Wow, I would have thought that would produce duplicate symbols, but it doesn't--that's really neat. I'm guessing the compiler still takes this as an indication to actually inline the method, though? – user4938472 Mar 24 '12 at 21:49
  • One way to look at the `inline` keyword is that it's a way to tell the compiler that the 'one definition rule' doesn't apply to this function. And yes, the compiler will generally attempt to inline the function, but it doesn't have to. – Michael Burr Mar 25 '12 at 02:10
  • I suppose the same effect could also be acheived (without the inling characteristic) through an abuse of templates? i.e. create a private `template void Bar::CircularDependency_impl(void)` and have CircularDependency() call that. The templated method then skirts the ODR, but can be implemented whenever it is convenient. – user4938472 Mar 25 '12 at 18:56
2

Circular dependencies are not really a problem as you can forward decalre that functions are inline. I wouldn't really suggest this, though: while the "source-less" approach initially makes it easier to use a library coming from somewhere, it causes longer compile times and tighter coupling between files. In a huge source base this is essentially killing the hOpe to get code build in reasonable times. Sure huge only starts at a couple millions lines of code but who cares about trivial programs...? (and, yes, the place I work at has several tens of millions lines of code being build into single executables)

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
2

My reasons for why this is a bad idea.

Increase in compile time

Every individual compilation unit that includes the header should compile all the header files in addition to the source in itself. This would probably increase the compile time and might be frustrating while testing your code, with small changes. One could argue the compiler might optimize it, but IMO it could not optimize it beyond a point.

Bigger code segment

If all the functions are written inline, it means that the compiler has to put all that code wherever the function is called. This is going to blow up the code segment and it would affect the load time of the program and the program would take more memory.

Creates dependency in client code with tight coupling

Whenever you change your implementation, every client should get updated (by re compiling the code). But if the implementation has been put in an independent shared object (.so or .dll), the client should just link to the new shared object.

Also I am not sure why one would do this.

//main.cpp
#define LIBFOO_COMPILE_INLINE
#include "libfoo.h"

If at all one has to do this, (s)he could have simply put the implementation code in main.cpp itself. Anyway, you could define LIBFOO_COMPILE_INLINE only in one compilation unit. Otherwise you will duplicate definitions.

I am actually much interested in developing a idiom to write cohesive template code. Sometime in future, C++ compiler should support writing cohesive templates. By this, I mean the client need not have to recompile the code, whenever template implementation in modified.

Senthil Babu
  • 1,243
  • 2
  • 11
  • 20
  • The `inline` directive does _not_ mean that "the compiler has to put all that code wherever the function is called". It's purely a _hint_ to the compiler. Whether it's actually inlined depends on compiler settings (with no optimizations enabled, generally nothing is inlined, and optimizing for size will cause less inlining except of trivial functions) and on the specific call site. – Jens Alfke Sep 19 '16 at 18:11