Changes between Version 4 and Version 5 of BestPracticeHandbook


Ignore:
Timestamp:
May 4, 2015, 12:59:23 PM (7 years ago)
Author:
Niall Douglas
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • BestPracticeHandbook

    v4 v5  
    8484
    8585
    86 == 2. Very strongly consider versioning your library's namespace ==
     86== 2. Very strongly consider versioning your library's namespace using inline namespaces and requesting users to alias a versioned namespace instead of using it directly ==
     87
     88C++ 11 adds a new feature called inline namespaces which are far more powerful than they first appear:
     89
     90{{{#!c++
     91namespace boost { namespace afio { inline namespace v1 { /* stuff */ } } }
     92// Stuff is generated at the ABI link layer in boost::afio::v1
     93// But to the compiler everything boost::afio::v1::* appears identically in boost::afio::*
     94// INCLUDING for ADL and overload resolution
     95// In other words you can declare your code in boost::afio::v1, and use it as if declared in boost::afio.
     96
     97// The other important new C++ 11 feature is namespace aliasing, so
     98namespace local { namespace afio = boost::afio; /* use afio::* and it all works */ }
     99}}}
     100
     101The reason this pattern is so useful is because it greatly eases the lives of your end users and you the library maintainer in years to come when you need to break API compatibility. Let's take a case example: imagine the situation typical in 03 C++ libraries where library Boost.Foo uses dependent library Boost.AFIO:
     102
     103{{{#!c++
     104namespace boost { namespace afio {
     105  struct foo {
     106    static void api(int i, double f);
     107  };
     108} }
     109...
     110namespace boost { namespace foo {
     111  boost::afio::api(1, 2);
     112} }
     113}}}
     114
     115Imagine that you now release an API breaking refactor of Boost.AFIO, which would look like this:
     116
     117{{{#!c++
     118namespace boost { namespace afio {
     119  struct foo {
     120    static void api(double f, int i);  // Oh dear, f and i have been swapped!
     121  };
     122} }
     123...
     124namespace boost { namespace foo {
     125  boost::afio::api(1, 2);  // This is probably now a bug!
     126} }
     127}}}
     128
     129The users of Boost.Foo which uses boost::afio::foo::api() now finds that their library no longer passes its unit testing because foo::api() has changed from before. They will quite rightly throw up a fuss, and under Boost's present rules you will be asked to roll back your refactor until Boost.Foo has also been refactored to match your refactor. This causes inconvenience for you the maintainer of Boost.AFIO, the maintainer of Boost.Foo, and is a general pain for users. It also breaks modularity, increases coupling between libraries in a way which saddles you the maintainer of Boost.AFIO with the lack of maintenance or failure of timely maintenance of libraries dependent on AFIO, and I can't strongly enough recommend you don't blindly copy the 03 idiom of suggesting client code use your library directly using fully qualified namespacing.
     130
     131The good news is we can make all this go away with inline namespaces and namespace aliasing, so consider this pattern instead:
     132
     133{{{#!c++
     134namespace boost { namespace afio { inline namespace v1 {
     135  struct foo {
     136    static void api(int i, double f);
     137  };
     138} } }
     139...
     140namespace boost { namespace foo {
     141  // Probably somewhere in this library's config.hpp
     142  namespace afio = boost::afio;  // This is the key use change which needs to be strongly recommended to your library's users
     143  ...
     144  // In implementation code after config.hpp
     145  afio::api(1, 2);  // Note the no longer fully qualified use of afio. The local namespace alias is used to "symlink" to "the latest" version of Boost.AFIO
     146} }
     147}}}
     148
     149Now imagine your refactor occurs as before:
     150
     151{{{#!c++
     152namespace boost { namespace afio {
     153  // Probably defined by boost/afio.hpp which in turn includes boost/afio_v2.hpp
     154  inline namespace v2 {
     155    struct foo {
     156      static void api(double f, int i);  // new implementation
     157    };
     158  }
     159  // Probably defined by boost/afio_v1.hpp
     160  namespace v1 {
     161    struct foo {
     162      static void api(int i, double f);  // old implementation
     163    };
     164  }
     165} }
     166...
     167namespace boost { namespace foo {
     168  // Probably somewhere in this library's config.hpp
     169  namespace afio = boost::afio::v1;  // By changing this one single line we "fix" the problem. Earlier we included <boost/afio_v1.hpp> instead of <boost/afio.hpp>.
     170  ...
     171  // In implementation code after config.hpp
     172  afio::api(1, 2);  // And this finds the v1 AFIO implementation, not the v2 implementation
     173} }
     174}}}
     175
     176What have we just achieved?
     177
     1781. Library Boost.Foo dependent on Boost.AFIO no longer requires lots of refactoring work if Boost.AFIO is refactored. Just two lines changed in its config.hpp, something easy for the release managers to do.
     1792. Library Boost.AFIO can now be evolved far quicker than before, and simply keep shipping entire copies of legacy versions without problems with colliding namespaces. As end users get round to upgrading, legacy versions can be removed from the distro after a period of warning.
     180
     181What are the problems with this technique?
     182
     1831. You now need to ship multiple copies of your library, maintain multiple copies of your library, and make sure simultaneous use of multiple library versions in the same executable doesn't conflict. I suspect this cost is worth it for the added flexibility for most library maintainers.
     1842. 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, 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.
     1853. 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.
     186
     187Some fun extra things this technique enables:
     188
     1891. Something not so obvious above is that you can also ''stub out'' fake copies of dependencies where that dependency is missing in the current config. For example, imagine optional compression support where your config.hpp either namespace aliases to boost::foo::compression either a real compression library, or an internal stub copy which actually does nothing. Your code is then written to assume a compression library aliased at boost::foo::compression and need not consider if it's actually there or not. The advantages here for reducing coupling are very obvious.
     1902. This technique is highly extensible to allow ''dependency injection'' of STL11 vs Boost on a per-feature basis e.g. your user wants Boost.Thread instead of STL11 thread but only for threading, so your library can be so modular as to allow both options to end users. This is covered in a separate recommendation below.
     191
    87192
    88193== 3. Strong consider trying your library on Microsoft Visual Studio 2015 ==
     
    100205* Non-static data member initialisers for aggregates.
    101206
    102 VS2015 is a very highly conforming C++ 11/14 compiler. It meets or exceeds clang 3.3 on every C++ feature. VS2015 even has some support for C++ 1z (C++ 17) matching clang 3.5. See http://blogs.msdn.com/b/vcblog/archive/2015/04/29/c-11-14-17-features-in-vs-2015-rc.aspx.
     207VS2015 is a very highly conforming C++ 11/14 compiler. It meets or exceeds clang 3.3 on every C++ feature, so if your library can be compiled by clang 3.3 then it is highly likely it should compile, without too much work, on VS2015. VS2015 even has some support for C++ 1z (C++ 17) matching about what clang 3.5 provides with the main showstoppers being lack of relaxed constexpr and no variable templates. See http://blogs.msdn.com/b/vcblog/archive/2015/04/29/c-11-14-17-features-in-vs-2015-rc.aspx.
    103208
    104209I am not claiming that you won't get surprises when you try getting MSVC to compile your code which you thought was standards compliant. MSVC is not an AST based compiler and uses heuristics to trigger partial AST compilation, and therefore has a unique processing model which exposes your assumptions about what you think or thought is valid C++. This is exactly why it is worthwhile getting your C++ 11/14 library working on MSVC because you will get a better quality, more standards conforming C++ library out of it.