Changes between Version 21 and Version 22 of BestPracticeHandbook
- Timestamp:
- May 12, 2015, 11:56:28 PM (7 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
BestPracticeHandbook
v21 v22 196 196 197 197 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.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. 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. 199 199 3. 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. 200 200 … … 304 304 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. 305 305 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.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 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. 307 307 308 308 … … 716 716 Finally, 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. 717 717 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 == 723 720 724 721 Most C++ programmers are aware of C++ template policy based design. This example is taken from https://en.wikipedia.org/wiki/Policy-based_design: … … 788 785 }}} 789 786 790 This works very well when your policy implementations fit nicely into template types, and whenthe 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:787 This 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 789 Consider 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: 793 790 794 791 {{{#!c++ … … 862 859 }}} 863 860 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 parameter861 The 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. 865 862 866 863 The 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. 867 864 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 == 875 879 876 880 Never block. Never call anything which blocks. … … 880 884 Automatic WinRT friendly. 881 885 882 == 1 9. 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 == 883 887 884 888 monolithic vs modular 885 889 git submodules vs biicode 886 890 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 ==