Minus The Header-Only Dogma
I've been working on the C++ Network Library for a few years already total but have been on-and-off with it until recently. One of the first decisions that I made in implementing the library was to try and do a header-only approach to implementing a network library implementation. At first it made perfect sense - you get the compiler to optimize as much of the code as it can while being able to expose a very friendly and expressive interface. When it was merely a handful of header files and there was just the notion of a message and the HTTP client, all was fine. I was perfectly happy showing off really simple but powerful use-cases where you'd want a C++ application to be able to make HTTP calls and get the contents without having any knowledge of how networks worked or how to program sockets on either Windows or UNIX-like systems. Until I got ambitious and it became a real project that people wanted to use - then reality became real.
It's not a secret that I like template metaprogramming. If you do a search on the site on ‘metaprogramming' you'll find a number of posts dating back to around 2007 detailing the cool things that can be done with C++ templates. I've seen it first hand make really crazy performance improvements in production while I was still working at Friendster. Until recently the server I re-wrote with almost the whole network layer being a huge template metaprogram was humming along nicely. Unfortunately people didn't have the luxury of three things that I had at that time:
- They don't have the same level of expertise with network programming. A vast majority of people who want to use cpp-netlib are people who don't want to implement the HTTP protocol from scratch or use a third-party library that was written in C. That said they didn't have the patience or experience necessarily to debug faults in cpp-netlib's implementation nor did they have the motivation to extend cpp-netlib.
- They don't have time to wait for a simple HTTP client example to compile. It just took too much time to try to iterate with cpp-netlib since it was header-only and exercised the compilers too much for something that should be relatively simple.
- They didn't buy into the header-only dogma. Much of the world was perfectly fine with linking to external libraries either dynamically or statically.
After getting feedback from BoostCon 2010 about the cost of header-only libraries, I then thought long and hard about how to introduce externally linked libraries into cpp-netlib. What came after that was a process of thinking about how to actually get a header-only library to a point where it can support both a header-only build and a non-header-only build. I imagine there aren't a lot of people who would encounter this problem - if it's even a problem at all - but I wanted to point out a couple of things that I stumbled upon while "meditating" on this issue.
Make it Optional
One of the first things I had to do was to make the header-only version of the library optional. The rationale for this is pretty simple: compiling template metaprograms costs compiler time and effort in terms of debugging, so if you want to pay this cost you explicitly say it. This makes casual users happier by not having to pay the heavy cost of the compile time every time. This makes sense if you're in development mode and doing lots of builds often enough to not want to wait too long for each build to finish.
In the C and C++ world, the way to make things optional at the source level is to make it a preprocessor macro. This is the first thing that made itself into the 0.9.0 release, the BOOST_NETWORK_NO_LIB macro which when defined makes cpp-netlib a header-only library. This made cpp-netlib a not-header-only-lib by default. This approach allows me (and other developers) to compile the heavy-compile-cost parts of the library into externally statically linked libs once and link them in at the end. The first things that went into the external lib were the Boost.Spirit based parsers. This had cut down the cost of compiling the tests dramatically - as much as 60% reduction in compile-time.
The approach has its merits because it makes users who want to use the library in header-only mode explicitly ask for it. One of the reasons people would want to do this is to enable further inlining that the compiler can do in header-only mode when building releases. During debug or development mode, linking an external library is just fine if you have a reasonably fast linker.
The good thing about being able to move things out into compiled external libraries is that you can program against interfaces. This means abstract base types and later binding. This is completely opposite to the original idea of using static dispatch for most everything that I wanted to achieve when I set out to implement cpp-netlib. When I looked at it long and hard, I saw that trying to do everything through static dispatch isn't so scalable in a number of ways:
- Extensibility was static. This meant that if you wanted to add an extension or alternate implementation, you'd have to hook into the library's static dispatch mechanisms. This is not trivial and only the bravest template metaprogrammers will even hazard a try to extend the library through the template dispatch mechanisms.
- Horizontally scaling was much impossible. This means adding alternate implementations was severely limited by the static nature of the bindings. For example, I had an idea of being able to statically determine which implementation of a particular string processing routine would be called given a tag type. I can see now that doing things this way makes adding support for things like CString (from Microsoft), QtString (from Qt), Boehm's cord/rope, and countless other string types tragically harder than if I use the normal generic programming approach instead of using type tag dispatch.
- Debugging is a pain. Not only does it take long to compile things, it takes long for the compiler to see/say where things went wrong. This makes debugging the library implementation hopelessly dependent on my patience on a given day. There were times when I just literally had no patience for compiler errors that I just left the code alone for days without touching it.
Now with the help of dynamic polymorphism I'm able to appropriately abstract some parts of the library that do need the runtime polymorphism - things like abstracting a connection whether it's SSL or a normal TCP/IP connection, and maybe later on support other underlying protocols like SCTP, and maybe even UDP if there's a use case for HTTP over UDP. This also makes testing the library much easier than it originally was.
Doing it this way also allows me and other implementors to leverage the best of both the static and the dynamic worlds in the same library. One of the ongoing refactoring efforts that I'm undertaking is the development of a mini HTTP REST Service framework which should come out early September. This would allow for developing a service in a traditional OOP fashion while having the efficiency of a header-only network implementation supporting the HTTP server implementation. The same technique allows us to expose a Boost.Parameter based API on the HTTP Client side and hiding much (if not all) of the internal client implementation in a Pimpl idiom - so the client implementation can remain template-heavy internally and can be compiled once, and linked to many times.
I personally am relieved that I now don't feel like I need to fight and feel the pain of being lonely in a template metaprogramming world. Although there are lots of template metaprogrammers now thanks to the excellent books and Boost C++ Library mailing lists, there is still a lot to be done to get template metaprogramming in the mainstream. I now think that the better strategy for getting it out in the hands of more people is by using the techniques and leveraging its power not just as a power tool but also as an adjunct to more traditional object-oriented programming techniques. I am going to be the first to point out that having been exposed to both OOP and template metaprogramming along with functional programming and generic programming that the synergies between/among these paradigms in C++ is what makes it such a unique and powerful programming language.
Hopefully those who are interested in joining and contributing to an open source library that explores the power and synergies of the different programming paradigms in C++ will join the C++ Network Library project. I now host a weekly Google+ Hangout every Wednesday 11am Sydney time (GMT +11) to discuss with developers and users of the library the direction of the project, what's going on, and how the project can proceed. I also regularly post updates to the C++ Network Library mailing list.
For the next release the target is to simplify much of the internal implementations to make it easier to test and understand for new-comers to the project, and along with that define how others might be able to take on more work in the library ranging from: testing, reporting issues, issue triage, updating the website, implementing new protocols, among many of the community/administrative work that comes with an open source project. We're targeting September 2 as the release date for version 0.10 which should feature better compile times and much better testing.
View All Articles by Dean Michael Berris
Our Daily Email of Breaking eBusiness News
About the Author:
Dean Michael Berris is the writer of C++ Soup! C++ Soup is a blog about what’s new, up-coming, and what’s going on with C++. C++ Developer with years of experience building high performance applications and implementing multi-threaded highly scalable systems.
WebProNews RSS Feed
More Expert Articles Articles