Changes between Version 21 and Version 22 of BestPracticeHandbook


Ignore:
Timestamp:
May 12, 2015, 11:56:28 PM (7 years ago)
Author:
Niall Douglas
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • BestPracticeHandbook

    v21 v22  
    196196
    197197 Also don't forget that git lets you recursively submodule yourself but pinned to a different branch by [https://git-scm.herokuapp.com/docs/gitmodules adding the `submodule.name.branch` stanza to .gitmodules], so if you do ship multiple versions you can mount specific version tracking branches of yourself within yourself such that a recursive submodule update checks out all the versions of yourself into a given checkout.
    198 2. The above technique alone is insufficient for header only end users where multiple versions of your library must coexist within the same translation unit. With some additional extra work using the preprocessor, it is possible to allow multiple header only library versions to also coexist in the same translation unit, but this is covered in a separate recommendation below.
     1982. The above technique alone is insufficient for header only end users where multiple versions of your library must coexist within the same translation unit. This is because almost every library header will have include guards, and these will prevent end users including alternative versions of your library within the same translation unit, even though those do not conflict at a C++ level due to having different namespaces. There is an additional problem in that most library headers define macros which will collide when you include multiple versions and may have (breaking) different values which induce misoperation. To this you can take a manual approach, and make sure that the header file for each version of your library has its own header guards and all macros are undefined on exit from a header. Or you can make use of another recommendation below which uses C preprocessor metaprogramming to automate header guard management for you.
    1991993. Many end users are not used to locally aliasing a library namespace in order to use it, and may continue to directly qualify it using the 03 idiom. You may consider defaulting to not using an inline namespace for the version to make sure users don't end up doing this in ways which hurt themselves, but that approach has both pros and cons.
    200200
     
    304304 Thread sanitiser (GCC and clang only):: If your library is capable of threaded use or your unit testing creates threads, you definitely should soak execute your unit tests with the thread sanitiser (`-fsanitize=thread`) for a few hours per week which provides a good quality check of the correct use of the C11/C++11 atomic memory model e.g. are all your atomic acquires matched with atomic releases in the right order? Did you read a memory location which was written concurrently without an acquire-release serialisation lock? Sadly the tool can't detect use of memory fences which substantially reduces your flexibility when writing with atomics, so do bear that in mind.
    305305
    306 Some may note I didn't recommend the address sanitiser (GCC and clang only). This is because you need to recompile your STL and libc with the address sanitiser to achieve perfect coverage, plus valgrind detects far more problems and valgrind detects bad code generated by the compiler and memory corruption by third party libraries. However if valgrind is just far too slow for your testing then employing the address sanitiser can be a useful substitute for valgrind for certain tests only. Note that the address sanitiser is perfect for untrusted input fuzz testing as it is much faster than valgrind, so I recommend the address sanitiser in the next section.
     306Some may note I didn't recommend the address sanitiser (GCC and clang only). This is because you need to recompile your STL and libc and all libraries with the address sanitiser to achieve perfect coverage, plus valgrind detects far more problems and valgrind detects bad code generated by the compiler and memory corruption by third party libraries. The only real additional feature of the address sanitiser over valgrind is that it can detect memory corruption within the stack, which valgrind never can. I personally have not found stack corruption much of a problem as programs inevitably crash when it happens. However if valgrind is just far too slow for your testing then employing the address sanitiser can be a useful substitute for valgrind for certain tests only. Note that the address sanitiser is perfect for untrusted input fuzz testing as it is much faster than valgrind, so I recommend the address sanitiser in the next section.
    307307
    308308
     
    716716Finally, I think that displaying status summaries via badges like this is another highly visible universal mark of software quality. It shows that the library author cares enough to publicly show the current state of their library. Future tooling by Boost which dashboards Boost libraries and/or ranks libraries by a quality score will almost certainly find the application specific ids for Travis, Appveyor, Coveralls etc by searching any Readme.md in the github repo for status badges, so by including status badges in your github Readme.md you can guarantee that such Boost library ranking scripts will work out of the box with no additional effort by you in the future.
    717717
    718 == 14. COUPLING: Consider enabling multiple versions of your (header only) library to coexist within the same translation unit ==
    719 
    720 todo
    721 
    722 == 15. DESIGN: Consider making (more) use of ADL C++ namespace composure as a design pattern ==
     718
     719== 14. DESIGN: Consider making (more) use of ADL C++ namespace composure as a design pattern ==
    723720
    724721Most C++ programmers are aware of C++ template policy based design. This example is taken from https://en.wikipedia.org/wiki/Policy-based_design:
     
    788785}}}
    789786
    790 This works very well when your policy implementations fit nicely into template types, and when the number of policy taking template types is reasonably low (otherwise you'll be doing a lot of typing as any changes to the policy design requires modifying every single instantiation of the policy taking template types). Another problem with policy based design is that it generates a lot of template instantiations, and generating a lot of template instantiations is bad because compilers often cannot do constant time type lookups and instead have linear or worse type lookups, so instantiating tens of million types is always going to be a lot slower to compile and sometimes link than millions of types.
    791 
    792 Consider instead doing an ADL based namespace composure design pattern which although just a different way of doing policy based design, can be highly effective in those niches where the traditional policy taking template approach falls down. Here is the same program above written using ADL namespace composure:
     787This works very well when (a) your policy implementations fit nicely into template types and (b) the number of policy taking template types is reasonably low (otherwise you'll be doing a lot of typing as any changes to the policy design requires modifying every single instantiation of the policy taking template types). Another problem with policy based design is that it generates a lot of template instantiations, and generating a lot of template instantiations is bad because compilers often cannot do constant time type lookups and instead have linear or worse type lookups, so instantiating tens of million types is always going to be a lot slower to compile and sometimes link than millions of types.
     788
     789Consider instead doing an ADL based namespace composure design pattern which is just a different way of doing policy based design. It can be highly effective in those niches where the traditional policy taking template approach falls down. Here is the same program above written using ADL namespace composure:
    793790
    794791{{{#!c++
     
    862859}}}
    863860
    864 The first example instantiates five types to be considered during global type lookup, whereas the second example instantiates no types at all at global lookup scope. The second example also requires no modification of each instantiation of the HelloWorld policy taking implementation in most cases of refactoring e.g. adding a third policy parameter
     861The first example instantiates five types to be thence considered during global type lookup, so let's say it has cost five to future code lookups. The second example instantiates no types at all at global lookup scope, so it has cost zero to future code lookups because all the types are declared inside namespaces normally not considered during global type lookups. The second example may also require less refactoring in the face of changes than the traditional form.
    865862
    866863The above pattern is in fact entirely C++ 03 code and uses no C++ 11. However, template aliasing in C++ 11 makes the above pattern much more flexible. Have a look at https://github.com/ptal/expected/blob/master/include/boost/functional/monads/rebindable.hpp for examples of this ADL invoked namespace composure design pattern.
    867864
    868 == 16. BUILD: Consider defaulting to header only, but make available C++ 11 features very substantially reducing build times ==
    869 
    870 Good proxy for supporting C++ Modules down the road, usually a massive build time improvement too especially on low end CPUs.
    871 
    872 == 17. COUPLING: Consider allowing your library users to dependency inject your dependencies on other libraries ==
    873 
    874 == 18. FUTURE PROOFING: Consider being C++ resumable function ready ==
     865== 15. BUILD: Consider defaulting to header only, but actively manage facilities for reducing build times ==
     866
     867||= Build flags =||= Microsoft Windows 8.1 x64 with Visual Studio 2013 =||= Ubuntu 14.04 LTS Linux x64 with GCC 4.9 and gold linker =||= Ubuntu 14.04 LTS Linux x64 with clang 3.4 and gold linker =||
     868|| Debug header only                   ||  7m17s || 12m0s  || 5m45s ||
     869|| Debug precompiled not header only   ||  0m55s ||  3m53s || asio failure ||
     870|| Release precompiled not header only ||  1m10s ||  3m22s || asio failure ||
     871|| Debug precompiled header only       ||  2m10s || 10m26s || 5m46s ||
     872|| Release precompiled header only     ||  2m58s ||  9m57s || 8m10s ||
     873|| Debug precompiled header only link time optimisation    || 5m37s || GCC ICE ||  5m42s ||
     874|| Release precompiled header only link time optimisation  || 7m30s || 13m0s   ||  8m11s ||
     875
     876== 16. COUPLING: Consider allowing your library users to dependency inject your dependencies on other libraries ==
     877
     878== 17. FUTURE PROOFING: Consider being C++ resumable function ready ==
    875879
    876880Never block. Never call anything which blocks.
     
    880884Automatic WinRT friendly.
    881885
    882 == 19. COUPLING/SOAPBOX: Essay about wisdom of defaulting to standalone capable (Boost) C++ 11/14 libraries with no external dependencies ==
     886== 18. COUPLING/SOAPBOX: Essay about wisdom of defaulting to standalone capable (Boost) C++ 11/14 libraries with no external dependencies ==
    883887
    884888monolithic vs modular
    885889git submodules vs biicode
    886890
    887 == 20. COUPLING/SOAPBOX: Essay about wisdom of dependency package managers in C++ 11/14 ==
     891== 19. COUPLING/SOAPBOX: Essay about wisdom of dependency package managers in C++ 11/14 ==