cpp
cpp copied to clipboard
Documentation on when/why/how to write `_impl.hpp`
@tmpsantos thanks for working on https://github.com/mapbox/geojson-cpp/pull/24. Now I'd like your help in documenting your work so others can do this again for further projects. Can you write up a quick guide on how you accomplished this, key terms, and why you chose _impl.hpp
as an extention (let's standardize on that). Drop your notes into this ticket as a comment please and then we'll integrate into the main readme.
/cc @mapsam @GretaCB
refs https://github.com/mapbox/hpp-skel/issues/3
Motivations
Header-only libraries are often easier to include in a project than external dependencies because they get compiled together with the project using them and they can just get bundled together with the project source files in a src/third-party
directory.
This will make sure they get compiled with the same compiler flags as you use for your project and you don't need to build a new dependency package for every new architecture/operating system combination you support.
C++ libraries heavily using templates make a good candidate for a header-only library because templates are often implemented completely in a header. This is true as long as the header-only library does not have a dependency by itself.
Making header-only optional, geojson-cpp
case
A library like geojson-cpp
is small enough to be a good candidate for a header-only library. The problem is it depends on rapidjson
, which is another header-only library.
Building it as a static library means we can hide the dependency on rapidjson
. Deploying it as a header-only library assumes the project using geojson-cpp
also depends on rapidjson
, which is the case for Mapbox GL Native but not exactly obvious for developers only interested on a GeoJSON parser.
The _impl.hpp
pattern
Fortunately there is a clean way to make a dependency be both a pre-compiled library and a header-only library, giving developers choice.
In the case of geojson-cpp
, the part of the project depending on rapidjson
got isolated in another header called geojson_impl.hpp
. The project has a geojson.cpp
that includes the implementation header:
#include <mapbox/geojson_impl.hpp>
Now the developer using geojson-cpp
as a project dependency has two choices:
-
- Include
geojson.hpp
and link withlibgeojson.a
.
- Include
or:
-
- Include
geojson.hpp
,geojson_impl.hpp
and make sure the project also has the headers forrapidjson
available in the include path.
- Include
Including geojson_impl.hpp
AND linking with libgeojson.a
will cause a link error because the symbols are going to get duplicated. Remember that libgeojson.a
was made from geojson.cpp
that includes geojson_impl.hpp
. It is like having geojson_impl.cpp
at the end of the day.
Mason package
mason
handles header-only packages differently from static/shared libraries. The header-only package is explicitly marked as MASON_HEADER_ONLY=true
on the package description script.
When packing a library that offers both options, the library has to be packaged twice for each release, as we did for geojson-cpp
:
https://github.com/mapbox/mason/blob/master/scripts/geojson/0.3.2/script.sh https://github.com/mapbox/mason/blob/master/scripts/geojson/0.3.2-hpp/script.sh
When publishing the package to AWS, they won't conflict because they get posted to different directories.
Using in a project with mason
and cmake
A project using geojson-cpp
static library would simple need to add the following line to the build system:
mason_use(geojson VERSION 0.3.2)
Another project using the header only version would have to do:
mason_use(rapidjson VERSION 1.1.0 HEADER_ONLY)
mason_use(geojson VERSION 0.3.2 HEADER_ONLY)
Note that rapidjson
is also needed in this case. Your project will also need to build a .cpp
file including geojson_impl.hpp
to satisfy the missing symbols like we have in Mapbox GL Native.
@springmeyer feel free to edit/add. Let me know if there is any other aspect of the _impl.hpp
you need better explained.
@tmpsantos this looks fantastic, thank you 🙏